mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
Risk score installation refactory (#142434)
* init * start transforms * error handling * onboarding * rename * restart transforms * cypress * update unit tests * update cypress task * add toastLifeTime * fix types * fix i18n * i18n * update cypress * update comment * Update x-pack/plugins/security_solution/server/lib/risk_score/transform/restart_transform.ts Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co> * Update x-pack/plugins/security_solution/public/risk_score/containers/onboarding/api/onboarding.ts Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co> * review * fix types * review * review * fix types * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' * fix types * cypress * update wording * update unit tests * update cypress * update cypress * [CI] Auto-commit changed files from 'node scripts/precommit_hook.js --ref HEAD~1..HEAD --fix' Co-authored-by: Pablo Machado <pablo.nevesmachado@elastic.co> Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
35879b9006
commit
0a39572938
52 changed files with 3103 additions and 699 deletions
|
@ -281,6 +281,7 @@ export const DETECTION_ENGINE_RULES_BULK_UPDATE =
|
|||
`${DETECTION_ENGINE_RULES_URL}/_bulk_update` as const;
|
||||
|
||||
export const INTERNAL_RISK_SCORE_URL = '/internal/risk_score' as const;
|
||||
export const RISK_SCORE_RESTART_TRANSFORMS = `${INTERNAL_RISK_SCORE_URL}/transforms/restart`;
|
||||
export const DEV_TOOL_PREBUILT_CONTENT =
|
||||
`${INTERNAL_RISK_SCORE_URL}/prebuilt_content/dev_tool/{console_id}` as const;
|
||||
export const devToolPrebuiltContentUrl = (spaceId: string, consoleId: string) =>
|
||||
|
@ -295,7 +296,6 @@ export const RISK_SCORE_CREATE_INDEX = `${INTERNAL_RISK_SCORE_URL}/indices/creat
|
|||
export const RISK_SCORE_DELETE_INDICES = `${INTERNAL_RISK_SCORE_URL}/indices/delete`;
|
||||
export const RISK_SCORE_CREATE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/create`;
|
||||
export const RISK_SCORE_DELETE_STORED_SCRIPT = `${INTERNAL_RISK_SCORE_URL}/stored_scripts/delete`;
|
||||
|
||||
/**
|
||||
* Internal detection engine routes
|
||||
*/
|
||||
|
|
27
x-pack/plugins/security_solution/common/types/risk_scores.ts
Normal file
27
x-pack/plugins/security_solution/common/types/risk_scores.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 interface ESProcessorConfig {
|
||||
on_failure?: Processor[];
|
||||
ignore_failure?: boolean;
|
||||
if?: string;
|
||||
tag?: string;
|
||||
[key: string]: unknown;
|
||||
}
|
||||
|
||||
export interface Processor {
|
||||
[typeName: string]: ESProcessorConfig;
|
||||
}
|
||||
|
||||
export interface Pipeline {
|
||||
name: string;
|
||||
description?: string;
|
||||
version?: number;
|
||||
processors: string | Processor[];
|
||||
on_failure?: Processor[];
|
||||
isManaged?: boolean;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
import { DEFAULT_ALERTS_INDEX } from '../constants';
|
||||
import { RiskScoreEntity, RiskScoreFields } from '../search_strategy';
|
||||
import type { Pipeline, Processor } from '../types/risk_scores';
|
||||
|
||||
/**
|
||||
* * Since 8.5, all the transforms, scripts,
|
||||
|
@ -203,8 +204,8 @@ export const getRiskScoreIngestPipelineOptions = (
|
|||
riskScoreEntity: RiskScoreEntity,
|
||||
spaceId = 'default',
|
||||
stringifyScript?: boolean
|
||||
) => {
|
||||
const processors = [
|
||||
): Pipeline => {
|
||||
const processors: Processor[] = [
|
||||
{
|
||||
set: {
|
||||
field: 'ingest_timestamp',
|
||||
|
@ -301,7 +302,6 @@ export const getCreateRiskScoreIndicesOptions = ({
|
|||
};
|
||||
};
|
||||
|
||||
/**
|
||||
/**
|
||||
* This should be aligned with
|
||||
* console_templates/enable_user_risk_score.console step 8
|
||||
|
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 { getNewRule } from '../../objects/rule';
|
||||
import {
|
||||
ENABLE_HOST_RISK_SCORE_BUTTON,
|
||||
ENABLE_USER_RISK_SCORE_BUTTON,
|
||||
RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST,
|
||||
RISK_SCORE_INSTALLATION_SUCCESS_TOAST,
|
||||
} from '../../screens/entity_analytics';
|
||||
import {
|
||||
deleteRiskScore,
|
||||
intercepInstallRiskScoreModule,
|
||||
waitForInstallRiskScoreModule,
|
||||
} from '../../tasks/api_calls/risk_scores';
|
||||
import { findSavedObjects } from '../../tasks/api_calls/risk_scores/saved_objects';
|
||||
import { createCustomRuleEnabled } from '../../tasks/api_calls/rules';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { login, visit } from '../../tasks/login';
|
||||
import { clickEnableRiskScore } from '../../tasks/risk_scores';
|
||||
import { RiskScoreEntity } from '../../tasks/risk_scores/common';
|
||||
import {
|
||||
getRiskScoreLatestTransformId,
|
||||
getRiskScorePivotTransformId,
|
||||
getTransformState,
|
||||
} from '../../tasks/risk_scores/transforms';
|
||||
import { ENTITY_ANALYTICS_URL } from '../../urls/navigation';
|
||||
|
||||
const spaceId = 'default';
|
||||
|
||||
describe('Enable risk scores', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
createCustomRuleEnabled(getNewRule(), 'rule1');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true });
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true });
|
||||
visit(ENTITY_ANALYTICS_URL);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true });
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true });
|
||||
});
|
||||
|
||||
it('shows enable host risk button', () => {
|
||||
cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('exist');
|
||||
});
|
||||
|
||||
it('should install host risk score successfully', () => {
|
||||
intercepInstallRiskScoreModule();
|
||||
clickEnableRiskScore(RiskScoreEntity.host);
|
||||
waitForInstallRiskScoreModule();
|
||||
|
||||
cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('be.disabled');
|
||||
|
||||
cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist');
|
||||
cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist');
|
||||
cy.get(ENABLE_HOST_RISK_SCORE_BUTTON).should('not.exist');
|
||||
getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
findSavedObjects(RiskScoreEntity.host, spaceId).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.saved_objects.length).to.eq(11);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows enable user risk button', () => {
|
||||
cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('exist');
|
||||
});
|
||||
|
||||
it('should install user risk score successfully', () => {
|
||||
intercepInstallRiskScoreModule();
|
||||
clickEnableRiskScore(RiskScoreEntity.user);
|
||||
waitForInstallRiskScoreModule();
|
||||
|
||||
cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('be.disabled');
|
||||
|
||||
cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist');
|
||||
|
||||
cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist');
|
||||
cy.get(ENABLE_USER_RISK_SCORE_BUTTON).should('not.exist');
|
||||
getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
|
||||
findSavedObjects(RiskScoreEntity.user, spaceId).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.saved_objects.length).to.eq(11);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,165 @@
|
|||
/*
|
||||
* 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 { getNewRule } from '../../objects/rule';
|
||||
import {
|
||||
RISK_SCORE_INSTALLATION_SUCCESS_TOAST,
|
||||
UPGRADE_HOST_RISK_SCORE_BUTTON,
|
||||
UPGRADE_USER_RISK_SCORE_BUTTON,
|
||||
UPGRADE_CANCELLATION_BUTTON,
|
||||
UPGRADE_CONFIRMARION_MODAL,
|
||||
RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST,
|
||||
} from '../../screens/entity_analytics';
|
||||
import { deleteRiskScore, installLegacyRiskScoreModule } from '../../tasks/api_calls/risk_scores';
|
||||
import { findSavedObjects } from '../../tasks/api_calls/risk_scores/saved_objects';
|
||||
import { createCustomRuleEnabled } from '../../tasks/api_calls/rules';
|
||||
import { cleanKibana } from '../../tasks/common';
|
||||
import { login, visit } from '../../tasks/login';
|
||||
import {
|
||||
clickUpgradeRiskScore,
|
||||
clickUpgradeRiskScoreConfirmed,
|
||||
interceptUpgradeRiskScoreModule,
|
||||
waitForUpgradeRiskScoreModule,
|
||||
} from '../../tasks/risk_scores';
|
||||
import { RiskScoreEntity } from '../../tasks/risk_scores/common';
|
||||
import {
|
||||
getRiskScoreLatestTransformId,
|
||||
getRiskScorePivotTransformId,
|
||||
getTransformState,
|
||||
} from '../../tasks/risk_scores/transforms';
|
||||
import { ENTITY_ANALYTICS_URL } from '../../urls/navigation';
|
||||
|
||||
const spaceId = 'default';
|
||||
|
||||
describe('Upgrade risk scores', () => {
|
||||
before(() => {
|
||||
cleanKibana();
|
||||
login();
|
||||
createCustomRuleEnabled(getNewRule(), 'rule1');
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true });
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true });
|
||||
installLegacyRiskScoreModule(RiskScoreEntity.host, spaceId);
|
||||
installLegacyRiskScoreModule(RiskScoreEntity.user, spaceId);
|
||||
visit(ENTITY_ANALYTICS_URL);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.host, spaceId, deleteAll: true });
|
||||
deleteRiskScore({ riskScoreEntity: RiskScoreEntity.user, spaceId, deleteAll: true });
|
||||
});
|
||||
|
||||
it('shows upgrade host risk button', () => {
|
||||
cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('be.visible');
|
||||
});
|
||||
|
||||
it('should show a confirmation modal for upgrading host risk score', () => {
|
||||
clickUpgradeRiskScore(RiskScoreEntity.host);
|
||||
cy.get(UPGRADE_CONFIRMARION_MODAL(RiskScoreEntity.host)).should('exist');
|
||||
});
|
||||
|
||||
it('display a link to host risk score Elastic doc', () => {
|
||||
clickUpgradeRiskScore(RiskScoreEntity.host);
|
||||
|
||||
cy.get(UPGRADE_CANCELLATION_BUTTON)
|
||||
.get(`${UPGRADE_CONFIRMARION_MODAL(RiskScoreEntity.host)} a`)
|
||||
.then((link) => {
|
||||
expect(link.prop('href')).to.eql(
|
||||
`https://www.elastic.co/guide/en/security/current/${RiskScoreEntity.host}-risk-score.html`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should upgrade host risk score successfully', () => {
|
||||
clickUpgradeRiskScore(RiskScoreEntity.host);
|
||||
|
||||
interceptUpgradeRiskScoreModule(RiskScoreEntity.host);
|
||||
|
||||
clickUpgradeRiskScoreConfirmed();
|
||||
waitForUpgradeRiskScoreModule();
|
||||
|
||||
cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('be.disabled');
|
||||
|
||||
cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist');
|
||||
cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.host)).should('exist');
|
||||
|
||||
cy.get(UPGRADE_HOST_RISK_SCORE_BUTTON).should('not.exist');
|
||||
getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
findSavedObjects(RiskScoreEntity.host, spaceId).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.saved_objects.length).to.eq(11);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows upgrade user risk button', () => {
|
||||
cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('be.visible');
|
||||
});
|
||||
|
||||
it('should show a confirmation modal for upgrading user risk score', () => {
|
||||
clickUpgradeRiskScore(RiskScoreEntity.user);
|
||||
cy.get(UPGRADE_CONFIRMARION_MODAL(RiskScoreEntity.user)).should('exist');
|
||||
});
|
||||
|
||||
it('display a link to user risk score Elastic doc', () => {
|
||||
clickUpgradeRiskScore(RiskScoreEntity.user);
|
||||
|
||||
cy.get(UPGRADE_CANCELLATION_BUTTON)
|
||||
.get(`${UPGRADE_CONFIRMARION_MODAL(RiskScoreEntity.user)} a`)
|
||||
.then((link) => {
|
||||
expect(link.prop('href')).to.eql(
|
||||
`https://www.elastic.co/guide/en/security/current/${RiskScoreEntity.user}-risk-score.html`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should upgrade user risk score successfully', () => {
|
||||
clickUpgradeRiskScore(RiskScoreEntity.user);
|
||||
interceptUpgradeRiskScoreModule(RiskScoreEntity.user);
|
||||
clickUpgradeRiskScoreConfirmed();
|
||||
waitForUpgradeRiskScoreModule();
|
||||
cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('be.disabled');
|
||||
|
||||
cy.get(RISK_SCORE_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist');
|
||||
cy.get(RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST(RiskScoreEntity.user)).should('exist');
|
||||
|
||||
cy.get(UPGRADE_USER_RISK_SCORE_BUTTON).should('not.exist');
|
||||
getTransformState(getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
getTransformState(getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId)).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.transforms[0].id).to.eq(
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId)
|
||||
);
|
||||
expect(res.body.transforms[0].state).to.eq('started');
|
||||
});
|
||||
|
||||
findSavedObjects(RiskScoreEntity.user, spaceId).then((res) => {
|
||||
expect(res.status).to.eq(200);
|
||||
expect(res.body.saved_objects.length).to.eq(11);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { RiskScoreEntity } from '../tasks/risk_scores/common';
|
||||
|
||||
export const ENABLE_HOST_RISK_SCORE_BUTTON = '[data-test-subj="enable_host_risk_score"]';
|
||||
|
||||
export const UPGRADE_HOST_RISK_SCORE_BUTTON = '[data-test-subj="host-risk-score-upgrade"]';
|
||||
|
@ -17,6 +19,13 @@ export const HOST_RISK_SCORE_NO_DATA_DETECTED =
|
|||
export const USER_RISK_SCORE_NO_DATA_DETECTED =
|
||||
'[data-test-subj="user-risk-score-no-data-detected"]';
|
||||
|
||||
export const RISK_SCORE_DASHBOARDS_INSTALLATION_SUCCESS_TOAST = (
|
||||
riskScoreEntity: RiskScoreEntity
|
||||
) => `[data-test-subj="${riskScoreEntity}RiskScoreDashboardsSuccessToast"]`;
|
||||
|
||||
export const RISK_SCORE_INSTALLATION_SUCCESS_TOAST = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`[data-test-subj="${riskScoreEntity}EnableSuccessToast"]`;
|
||||
|
||||
export const HOSTS_DONUT_CHART =
|
||||
'[data-test-subj="entity_analytics_hosts"] [data-test-subj="donut-chart"]';
|
||||
|
||||
|
@ -37,3 +46,10 @@ export const ANOMALIES_TABLE =
|
|||
'[data-test-subj="entity_analytics_anomalies"] #entityAnalyticsDashboardAnomaliesTable';
|
||||
|
||||
export const ANOMALIES_TABLE_ROWS = '[data-test-subj="entity_analytics_anomalies"] .euiTableRow';
|
||||
|
||||
export const UPGRADE_CONFIRMARION_MODAL = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`[data-test-subj="${riskScoreEntity}-risk-score-upgrade-confirmation-modal"]`;
|
||||
|
||||
export const UPGRADE_CONFIRMATION_BUTTON = '[data-test-subj="confirmModalConfirmButton"]';
|
||||
|
||||
export const UPGRADE_CANCELLATION_BUTTON = '[data-test-subj="confirmModalCancelButton"]';
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
/*
|
||||
* 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 { ENTITY_ANALYTICS_URL } from '../../../urls/navigation';
|
||||
import { RISK_SCORE_URL } from '../../../urls/risk_score';
|
||||
import { visit } from '../../login';
|
||||
import { RiskScoreEntity } from '../../risk_scores/common';
|
||||
import {
|
||||
getCreateLegacyRiskScoreIndicesOptions,
|
||||
getCreateLegacyRiskScoreLatestIndicesOptions,
|
||||
} from '../../risk_scores/indices';
|
||||
import {
|
||||
getIngestPipelineName,
|
||||
getLegacyIngestPipelineName,
|
||||
getLegacyRiskScoreIngestPipelineOptions,
|
||||
} from '../../risk_scores/ingest_pipelines';
|
||||
import {
|
||||
getLegacyRiskHostCreateInitScriptOptions,
|
||||
getLegacyRiskHostCreateLevelScriptOptions,
|
||||
getLegacyRiskHostCreateMapScriptOptions,
|
||||
getLegacyRiskHostCreateReduceScriptOptions,
|
||||
getLegacyRiskScoreInitScriptId,
|
||||
getLegacyRiskScoreLevelScriptId,
|
||||
getLegacyRiskScoreMapScriptId,
|
||||
getLegacyRiskScoreReduceScriptId,
|
||||
getLegacyRiskUserCreateLevelScriptOptions,
|
||||
getLegacyRiskUserCreateMapScriptOptions,
|
||||
getLegacyRiskUserCreateReduceScriptOptions,
|
||||
getRiskScoreInitScriptId,
|
||||
getRiskScoreLevelScriptId,
|
||||
getRiskScoreMapScriptId,
|
||||
getRiskScoreReduceScriptId,
|
||||
} from '../../risk_scores/stored_scripts';
|
||||
import {
|
||||
createTransform,
|
||||
deleteTransforms,
|
||||
getCreateLegacyLatestTransformOptions,
|
||||
getCreateLegacyMLHostPivotTransformOptions,
|
||||
getCreateLegacyMLUserPivotTransformOptions,
|
||||
getRiskScoreLatestTransformId,
|
||||
getRiskScorePivotTransformId,
|
||||
startTransforms,
|
||||
} from '../../risk_scores/transforms';
|
||||
import { createIndex, deleteRiskScoreIndicies } from './indices';
|
||||
import { createIngestPipeline, deleteRiskScoreIngestPipelines } from './ingest_pipelines';
|
||||
import { deleteSavedObjects } from './saved_objects';
|
||||
import { createStoredScript, deleteStoredScripts } from './stored_scripts';
|
||||
|
||||
/**
|
||||
* @deleteAll: If set to true, it deletes both old and new version.
|
||||
* If set to false, it deletes legacy version only.
|
||||
*/
|
||||
export const deleteRiskScore = ({
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
deleteAll,
|
||||
}: {
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
spaceId?: string;
|
||||
deleteAll: boolean;
|
||||
}) => {
|
||||
const transformIds = [
|
||||
getRiskScorePivotTransformId(riskScoreEntity, spaceId),
|
||||
getRiskScoreLatestTransformId(riskScoreEntity, spaceId),
|
||||
];
|
||||
const legacyIngestPipelineNames = [getLegacyIngestPipelineName(riskScoreEntity)];
|
||||
const ingestPipelinesNames = deleteAll
|
||||
? [...legacyIngestPipelineNames, getIngestPipelineName(riskScoreEntity, spaceId)]
|
||||
: legacyIngestPipelineNames;
|
||||
|
||||
const legacyScriptIds = [
|
||||
...(riskScoreEntity === RiskScoreEntity.host
|
||||
? [getLegacyRiskScoreInitScriptId(riskScoreEntity)]
|
||||
: []),
|
||||
getLegacyRiskScoreLevelScriptId(riskScoreEntity),
|
||||
getLegacyRiskScoreMapScriptId(riskScoreEntity),
|
||||
getLegacyRiskScoreReduceScriptId(riskScoreEntity),
|
||||
];
|
||||
const scripts = deleteAll
|
||||
? [
|
||||
...legacyScriptIds,
|
||||
...(riskScoreEntity === RiskScoreEntity.host
|
||||
? [getRiskScoreInitScriptId(riskScoreEntity, spaceId)]
|
||||
: []),
|
||||
getRiskScoreLevelScriptId(riskScoreEntity, spaceId),
|
||||
getRiskScoreMapScriptId(riskScoreEntity, spaceId),
|
||||
getRiskScoreReduceScriptId(riskScoreEntity, spaceId),
|
||||
]
|
||||
: legacyScriptIds;
|
||||
|
||||
deleteTransforms(transformIds);
|
||||
deleteRiskScoreIngestPipelines(ingestPipelinesNames);
|
||||
deleteStoredScripts(scripts);
|
||||
deleteSavedObjects(`${riskScoreEntity}RiskScoreDashboards`, deleteAll);
|
||||
deleteRiskScoreIndicies(riskScoreEntity, spaceId);
|
||||
};
|
||||
|
||||
const installLegacyHostRiskScoreModule = (spaceId: string) => {
|
||||
/**
|
||||
* Step 1 Upload script: ml_hostriskscore_levels_script
|
||||
*/
|
||||
createStoredScript(getLegacyRiskHostCreateLevelScriptOptions())
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 2 Upload script: ml_hostriskscore_init_script
|
||||
*/
|
||||
return createStoredScript(getLegacyRiskHostCreateInitScriptOptions());
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 3 Upload script: ml_hostriskscore_map_script
|
||||
*/
|
||||
return createStoredScript(getLegacyRiskHostCreateMapScriptOptions());
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 4 Upload script: ml_hostriskscore_reduce_script
|
||||
*/
|
||||
return createStoredScript(getLegacyRiskHostCreateReduceScriptOptions());
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 5 Upload the ingest pipeline: ml_hostriskscore_ingest_pipeline
|
||||
*/
|
||||
return createIngestPipeline(getLegacyRiskScoreIngestPipelineOptions(RiskScoreEntity.host));
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 6 create ml_host_risk_score_{spaceId} index
|
||||
*/
|
||||
return createIndex(
|
||||
getCreateLegacyRiskScoreIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 7 create transform: ml_hostriskscore_pivot_transform_{spaceId}
|
||||
*/
|
||||
return createTransform(
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId),
|
||||
getCreateLegacyMLHostPivotTransformOptions({ spaceId })
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 8 create ml_host_risk_score_latest_{spaceId} index
|
||||
*/
|
||||
return createIndex(
|
||||
getCreateLegacyRiskScoreLatestIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 9 create transform: ml_hostriskscore_latest_transform_{spaceId}
|
||||
*/
|
||||
return createTransform(
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId),
|
||||
getCreateLegacyLatestTransformOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 10 Start the pivot transform
|
||||
* Step 11 Start the latest transform
|
||||
*/
|
||||
const transformIds = [
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId),
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId),
|
||||
];
|
||||
return startTransforms(transformIds);
|
||||
})
|
||||
.then(() => {
|
||||
// refresh page
|
||||
visit(ENTITY_ANALYTICS_URL);
|
||||
});
|
||||
};
|
||||
|
||||
const installLegacyUserRiskScoreModule = async (spaceId = 'default') => {
|
||||
/**
|
||||
* Step 1 Upload script: ml_userriskscore_levels_script
|
||||
*/
|
||||
createStoredScript(getLegacyRiskUserCreateLevelScriptOptions())
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 2 Upload script: ml_userriskscore_map_script
|
||||
*/
|
||||
return createStoredScript(getLegacyRiskUserCreateMapScriptOptions());
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 3 Upload script: ml_userriskscore_reduce_script
|
||||
*/
|
||||
return createStoredScript(getLegacyRiskUserCreateReduceScriptOptions());
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 4 Upload ingest pipeline: ml_userriskscore_ingest_pipeline
|
||||
*/
|
||||
return createIngestPipeline(getLegacyRiskScoreIngestPipelineOptions(RiskScoreEntity.user));
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 5 create ml_user_risk_score_{spaceId} index
|
||||
*/
|
||||
return createIndex(
|
||||
getCreateLegacyRiskScoreIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 6 create Transform: ml_userriskscore_pivot_transform_{spaceId}
|
||||
*/
|
||||
return createTransform(
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId),
|
||||
getCreateLegacyMLUserPivotTransformOptions({ spaceId })
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 7 create ml_user_risk_score_latest_{spaceId} index
|
||||
*/
|
||||
return createIndex(
|
||||
getCreateLegacyRiskScoreLatestIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 8 create Transform: ml_userriskscore_latest_transform_{spaceId}
|
||||
*/
|
||||
return createTransform(
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId),
|
||||
getCreateLegacyLatestTransformOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
})
|
||||
);
|
||||
})
|
||||
.then(() => {
|
||||
/**
|
||||
* Step 9 Start the pivot transform
|
||||
* Step 10 Start the latest transform
|
||||
*/
|
||||
const transformIds = [
|
||||
getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId),
|
||||
getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId),
|
||||
];
|
||||
return startTransforms(transformIds);
|
||||
})
|
||||
.then(() => {
|
||||
visit(ENTITY_ANALYTICS_URL);
|
||||
});
|
||||
};
|
||||
|
||||
export const installLegacyRiskScoreModule = (
|
||||
riskScoreEntity: RiskScoreEntity,
|
||||
spaceId = 'default'
|
||||
) => {
|
||||
if (riskScoreEntity === RiskScoreEntity.user) {
|
||||
installLegacyUserRiskScoreModule(spaceId);
|
||||
} else {
|
||||
installLegacyHostRiskScoreModule(spaceId);
|
||||
}
|
||||
};
|
||||
|
||||
export const intercepInstallRiskScoreModule = () => {
|
||||
cy.intercept(`POST`, RISK_SCORE_URL).as('install');
|
||||
};
|
||||
|
||||
export const waitForInstallRiskScoreModule = () => {
|
||||
cy.wait(['@install'], { requestTimeout: 50000 });
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { INDICES_URL } from '../../../urls/risk_score';
|
||||
import type { RiskScoreEntity } from '../../risk_scores/common';
|
||||
import { getLatestTransformIndex, getPivotTransformIndex } from '../../risk_scores/indices';
|
||||
|
||||
export const createIndex = (options: {
|
||||
index: string;
|
||||
mappings: string | Record<string, unknown>;
|
||||
}) => {
|
||||
return cy.request({
|
||||
method: 'put',
|
||||
url: `${INDICES_URL}/create`,
|
||||
body: options,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteRiskScoreIndicies = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') => {
|
||||
return cy
|
||||
.request({
|
||||
method: 'post',
|
||||
url: `${INDICES_URL}/delete`,
|
||||
body: {
|
||||
indices: [getPivotTransformIndex(riskScoreEntity, spaceId)],
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
failOnStatusCode: false,
|
||||
})
|
||||
.then(() => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${INDICES_URL}/delete`,
|
||||
body: {
|
||||
indices: [getLatestTransformIndex(riskScoreEntity, spaceId)],
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { INGEST_PIPELINES_URL } from '../../../urls/risk_score';
|
||||
|
||||
export const createIngestPipeline = (options: { name: string; processors: Array<{}> }) => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${INGEST_PIPELINES_URL}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
body: options,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteRiskScoreIngestPipelines = (names: string[]) => {
|
||||
return cy.request({
|
||||
method: 'delete',
|
||||
url: `${INGEST_PIPELINES_URL}/${names.join(',')}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
failOnStatusCode: false,
|
||||
});
|
||||
};
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* 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 { RISK_SCORE_SAVED_OBJECTS_URL, SAVED_OBJECTS_URL } from '../../../urls/risk_score';
|
||||
import type { RiskScoreEntity } from '../../risk_scores/common';
|
||||
import { getRiskScoreTagName } from '../../risk_scores/saved_objects';
|
||||
|
||||
export const deleteSavedObjects = (
|
||||
templateName: `${RiskScoreEntity}RiskScoreDashboards`,
|
||||
deleteAll: boolean
|
||||
) => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${RISK_SCORE_SAVED_OBJECTS_URL}/_bulk_delete/${templateName}`,
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
deleteAll,
|
||||
},
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
});
|
||||
};
|
||||
|
||||
export const createSavedObjects = (templateName: `${RiskScoreEntity}RiskScoreDashboards`) => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${RISK_SCORE_SAVED_OBJECTS_URL}/_bulk_create/${templateName}`,
|
||||
failOnStatusCode: false,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
});
|
||||
};
|
||||
|
||||
export const findSavedObjects = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') => {
|
||||
const search = getRiskScoreTagName(riskScoreEntity, spaceId);
|
||||
|
||||
const getReference = (tagId: string) => encodeURIComponent(`[{"type":"tag","id":"${tagId}"}]`);
|
||||
|
||||
return cy
|
||||
.request({
|
||||
method: 'get',
|
||||
url: `${SAVED_OBJECTS_URL}/_find?fields=id&type=tag&sort_field=updated_at&search=${search}&search_fields=name`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
})
|
||||
.then((res) =>
|
||||
cy.request({
|
||||
method: 'get',
|
||||
url: `${SAVED_OBJECTS_URL}/_find?fields=id&type=index-pattern&type=tag&type=visualization&type=dashboard&type=lens&sort_field=updated_at&has_reference=${getReference(
|
||||
res.body.saved_objects[0].id
|
||||
)}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
})
|
||||
);
|
||||
};
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* 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 { STORED_SCRIPTS_URL } from '../../../urls/risk_score';
|
||||
|
||||
export const createStoredScript = (options: { id: string; script: {} }) => {
|
||||
return cy.request({
|
||||
method: 'put',
|
||||
url: `${STORED_SCRIPTS_URL}/create`,
|
||||
body: options,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
});
|
||||
};
|
||||
|
||||
const deleteStoredScript = (id: string) => {
|
||||
return cy.request({
|
||||
method: 'delete',
|
||||
url: `${STORED_SCRIPTS_URL}/delete`,
|
||||
body: { id },
|
||||
failOnStatusCode: false,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteStoredScripts = async (scriptIds: string[]) => {
|
||||
await Promise.all(scriptIds.map((scriptId) => deleteStoredScript(scriptId)));
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* 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 enum RiskScoreEntity {
|
||||
host = 'host',
|
||||
user = 'user',
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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 {
|
||||
ENABLE_HOST_RISK_SCORE_BUTTON,
|
||||
ENABLE_USER_RISK_SCORE_BUTTON,
|
||||
UPGRADE_CONFIRMATION_BUTTON,
|
||||
UPGRADE_HOST_RISK_SCORE_BUTTON,
|
||||
UPGRADE_USER_RISK_SCORE_BUTTON,
|
||||
} from '../../screens/entity_analytics';
|
||||
import {
|
||||
INGEST_PIPELINES_URL,
|
||||
RISK_SCORE_SAVED_OBJECTS_URL,
|
||||
STORED_SCRIPTS_URL,
|
||||
TRANSFORMS_URL,
|
||||
} from '../../urls/risk_score';
|
||||
import { intercepInstallRiskScoreModule } from '../api_calls/risk_scores';
|
||||
|
||||
import { RiskScoreEntity } from './common';
|
||||
import { getLegacyIngestPipelineName } from './ingest_pipelines';
|
||||
|
||||
export const interceptUpgradeRiskScoreModule = (riskScoreEntity: RiskScoreEntity) => {
|
||||
cy.intercept(
|
||||
`POST`,
|
||||
`${RISK_SCORE_SAVED_OBJECTS_URL}/_bulk_delete/${riskScoreEntity}RiskScoreDashboards`
|
||||
).as('deleteDashboards');
|
||||
cy.intercept(`POST`, `${TRANSFORMS_URL}/stop_transforms`).as('stopTransforms');
|
||||
cy.intercept(`POST`, `${TRANSFORMS_URL}/delete_transforms`).as('deleteTransforms');
|
||||
cy.intercept(
|
||||
`DELETE`,
|
||||
`${INGEST_PIPELINES_URL}/${getLegacyIngestPipelineName(riskScoreEntity)}`
|
||||
).as('deleteIngestPipelines');
|
||||
cy.intercept(`DELETE`, `${STORED_SCRIPTS_URL}/delete`).as('deleteScripts');
|
||||
intercepInstallRiskScoreModule();
|
||||
};
|
||||
|
||||
export const waitForUpgradeRiskScoreModule = () => {
|
||||
cy.wait(
|
||||
[
|
||||
'@deleteDashboards',
|
||||
'@stopTransforms',
|
||||
'@deleteTransforms',
|
||||
'@deleteIngestPipelines',
|
||||
'@deleteScripts',
|
||||
'@install',
|
||||
],
|
||||
{ requestTimeout: 50000 }
|
||||
);
|
||||
};
|
||||
|
||||
export const clickEnableRiskScore = (riskScoreEntity: RiskScoreEntity) => {
|
||||
const button =
|
||||
riskScoreEntity === RiskScoreEntity.user
|
||||
? ENABLE_USER_RISK_SCORE_BUTTON
|
||||
: ENABLE_HOST_RISK_SCORE_BUTTON;
|
||||
|
||||
cy.get(button).click();
|
||||
};
|
||||
|
||||
export const clickUpgradeRiskScore = (riskScoreEntity: RiskScoreEntity) => {
|
||||
const button =
|
||||
riskScoreEntity === RiskScoreEntity.user
|
||||
? UPGRADE_USER_RISK_SCORE_BUTTON
|
||||
: UPGRADE_HOST_RISK_SCORE_BUTTON;
|
||||
|
||||
cy.get(button).click();
|
||||
};
|
||||
|
||||
export const clickUpgradeRiskScoreConfirmed = () => {
|
||||
cy.get(UPGRADE_CONFIRMATION_BUTTON).click();
|
||||
};
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 { RiskScoreEntity } from './common';
|
||||
|
||||
export const getPivotTransformIndex = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}_risk_score_${spaceId}`;
|
||||
|
||||
export const getLatestTransformIndex = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}_risk_score_latest_${spaceId}`;
|
||||
|
||||
export const getCreateLegacyRiskScoreIndicesOptions = ({
|
||||
spaceId = 'default',
|
||||
riskScoreEntity,
|
||||
}: {
|
||||
spaceId?: string;
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
}) => {
|
||||
const mappings = {
|
||||
properties: {
|
||||
[`${riskScoreEntity}.name`]: {
|
||||
type: 'keyword',
|
||||
},
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
ingest_timestamp: {
|
||||
type: 'date',
|
||||
},
|
||||
risk: {
|
||||
type: 'text',
|
||||
fields: {
|
||||
keyword: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
risk_stats: {
|
||||
properties: {
|
||||
risk_score: {
|
||||
type: 'float',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
index: getPivotTransformIndex(riskScoreEntity, spaceId),
|
||||
mappings,
|
||||
};
|
||||
};
|
||||
|
||||
export const getCreateLegacyRiskScoreLatestIndicesOptions = ({
|
||||
spaceId = 'default',
|
||||
riskScoreEntity,
|
||||
}: {
|
||||
spaceId?: string;
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
}) => {
|
||||
const mappings = {
|
||||
properties: {
|
||||
[`${riskScoreEntity}.name`]: {
|
||||
type: 'keyword',
|
||||
},
|
||||
'@timestamp': {
|
||||
type: 'date',
|
||||
},
|
||||
ingest_timestamp: {
|
||||
type: 'date',
|
||||
},
|
||||
risk: {
|
||||
type: 'text',
|
||||
fields: {
|
||||
keyword: {
|
||||
type: 'keyword',
|
||||
},
|
||||
},
|
||||
},
|
||||
risk_stats: {
|
||||
properties: {
|
||||
risk_score: {
|
||||
type: 'float',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
return {
|
||||
index: getLatestTransformIndex(riskScoreEntity, spaceId),
|
||||
mappings,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* 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 { RiskScoreEntity } from './common';
|
||||
import { getLegacyRiskScoreLevelScriptId } from './stored_scripts';
|
||||
|
||||
export const getIngestPipelineName = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}riskscore_ingest_pipeline_${spaceId}`;
|
||||
|
||||
export const getLegacyIngestPipelineName = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`ml_${riskScoreEntity}riskscore_ingest_pipeline`;
|
||||
|
||||
export const getLegacyRiskScoreIngestPipelineOptions = (riskScoreEntity: RiskScoreEntity) => {
|
||||
const processors = [
|
||||
{
|
||||
set: {
|
||||
field: 'ingest_timestamp',
|
||||
value: '{{_ingest.timestamp}}',
|
||||
},
|
||||
},
|
||||
{
|
||||
fingerprint: {
|
||||
fields: ['@timestamp', '_id'],
|
||||
method: 'SHA-256',
|
||||
target_field: '_id',
|
||||
},
|
||||
},
|
||||
{
|
||||
script: {
|
||||
id: getLegacyRiskScoreLevelScriptId(riskScoreEntity),
|
||||
params: {
|
||||
risk_score: 'risk_stats.risk_score',
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
return {
|
||||
name: getLegacyIngestPipelineName(riskScoreEntity),
|
||||
processors,
|
||||
};
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* 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 { RiskScoreEntity } from './common';
|
||||
|
||||
const HOST_RISK_SCORE = 'Host Risk Score';
|
||||
const USER_RISK_SCORE = 'User Risk Score';
|
||||
|
||||
const getRiskScore = (riskScoreEntity: RiskScoreEntity) =>
|
||||
riskScoreEntity === RiskScoreEntity.user ? USER_RISK_SCORE : HOST_RISK_SCORE;
|
||||
|
||||
export const getRiskScoreTagName = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`${getRiskScore(riskScoreEntity)} ${spaceId}`;
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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 { RiskScoreEntity } from './common';
|
||||
|
||||
export const getRiskScoreLevelScriptId = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}riskscore_levels_script_${spaceId}`;
|
||||
export const getRiskScoreInitScriptId = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}riskscore_init_script_${spaceId}`;
|
||||
export const getRiskScoreMapScriptId = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}riskscore_map_script_${spaceId}`;
|
||||
export const getRiskScoreReduceScriptId = (riskScoreEntity: RiskScoreEntity, spaceId = 'default') =>
|
||||
`ml_${riskScoreEntity}riskscore_reduce_script_${spaceId}`;
|
||||
|
||||
export const getLegacyRiskScoreLevelScriptId = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`ml_${riskScoreEntity}riskscore_levels_script`;
|
||||
export const getLegacyRiskScoreInitScriptId = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`ml_${riskScoreEntity}riskscore_init_script`;
|
||||
export const getLegacyRiskScoreMapScriptId = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`ml_${riskScoreEntity}riskscore_map_script`;
|
||||
export const getLegacyRiskScoreReduceScriptId = (riskScoreEntity: RiskScoreEntity) =>
|
||||
`ml_${riskScoreEntity}riskscore_reduce_script`;
|
||||
|
||||
export const getLegacyRiskHostCreateLevelScriptOptions = (stringifyScript?: boolean) => {
|
||||
const source =
|
||||
"double risk_score = (def)ctx.getByPath(params.risk_score);\nif (risk_score < 20) {\n ctx['risk'] = 'Unknown'\n}\nelse if (risk_score >= 20 && risk_score < 40) {\n ctx['risk'] = 'Low'\n}\nelse if (risk_score >= 40 && risk_score < 70) {\n ctx['risk'] = 'Moderate'\n}\nelse if (risk_score >= 70 && risk_score < 90) {\n ctx['risk'] = 'High'\n}\nelse if (risk_score >= 90) {\n ctx['risk'] = 'Critical'\n}";
|
||||
return {
|
||||
id: getLegacyRiskScoreLevelScriptId(RiskScoreEntity.host),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: stringifyScript ? JSON.stringify(source) : source,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegacyRiskHostCreateInitScriptOptions = (stringifyScript?: boolean) => {
|
||||
const source =
|
||||
'state.rule_risk_stats = new HashMap();\nstate.host_variant_set = false;\nstate.host_variant = new String();\nstate.tactic_ids = new HashSet();';
|
||||
return {
|
||||
id: getLegacyRiskScoreInitScriptId(RiskScoreEntity.host),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: stringifyScript ? JSON.stringify(source) : source,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegacyRiskHostCreateMapScriptOptions = (stringifyScript?: boolean) => {
|
||||
const source =
|
||||
'// Get the host variant\nif (state.host_variant_set == false) {\n if (doc.containsKey("host.os.full") && doc["host.os.full"].size() != 0) {\n state.host_variant = doc["host.os.full"].value;\n state.host_variant_set = true;\n }\n}\n// Aggregate all the tactics seen on the host\nif (doc.containsKey("signal.rule.threat.tactic.id") && doc["signal.rule.threat.tactic.id"].size() != 0) {\n state.tactic_ids.add(doc["signal.rule.threat.tactic.id"].value);\n}\n// Get running sum of time-decayed risk score per rule name per shard\nString rule_name = doc["signal.rule.name"].value;\ndef stats = state.rule_risk_stats.getOrDefault(rule_name, [0.0,"",false]);\nint time_diff = (int)((System.currentTimeMillis() - doc["@timestamp"].value.toInstant().toEpochMilli()) / (1000.0 * 60.0 * 60.0));\ndouble risk_derate = Math.min(1, Math.exp((params.lookback_time - time_diff) / params.time_decay_constant));\nstats[0] = Math.max(stats[0], doc["signal.rule.risk_score"].value * risk_derate);\nif (stats[2] == false) {\n stats[1] = doc["kibana.alert.rule.uuid"].value;\n stats[2] = true;\n}\nstate.rule_risk_stats.put(rule_name, stats);';
|
||||
return {
|
||||
id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.host),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: stringifyScript ? JSON.stringify(source) : source,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegacyRiskHostCreateReduceScriptOptions = (stringifyScript?: boolean) => {
|
||||
const source =
|
||||
'// Consolidating time decayed risks and tactics from across all shards\nMap total_risk_stats = new HashMap();\nString host_variant = new String();\ndef tactic_ids = new HashSet();\nfor (state in states) {\n for (key in state.rule_risk_stats.keySet()) {\n def rule_stats = state.rule_risk_stats.get(key);\n def stats = total_risk_stats.getOrDefault(key, [0.0,"",false]);\n stats[0] = Math.max(stats[0], rule_stats[0]);\n if (stats[2] == false) {\n stats[1] = rule_stats[1];\n stats[2] = true;\n } \n total_risk_stats.put(key, stats);\n }\n if (host_variant.length() == 0) {\n host_variant = state.host_variant;\n }\n tactic_ids.addAll(state.tactic_ids);\n}\n// Consolidating individual rule risks and arranging them in decreasing order\nList risks = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n risks.add(total_risk_stats[key][0])\n}\nCollections.sort(risks, Collections.reverseOrder());\n// Calculating total host risk score\ndouble total_risk = 0.0;\ndouble risk_cap = params.max_risk * params.zeta_constant;\nfor (int i=0;i<risks.length;i++) {\n total_risk += risks[i] / Math.pow((1+i), params.p);\n}\n// Normalizing the host risk score\ndouble total_norm_risk = 100 * total_risk / risk_cap;\nif (total_norm_risk < 40) {\n total_norm_risk = 2.125 * total_norm_risk;\n}\nelse if (total_norm_risk >= 40 && total_norm_risk < 50) {\n total_norm_risk = 85 + (total_norm_risk - 40);\n}\nelse {\n total_norm_risk = 95 + (total_norm_risk - 50) / 10;\n}\n// Calculating multipliers to the host risk score\ndouble risk_multiplier = 1.0;\nList multipliers = new ArrayList();\n// Add a multiplier if host is a server\nif (host_variant.toLowerCase().contains("server")) {\n risk_multiplier *= params.server_multiplier;\n multipliers.add("Host is a server");\n}\n// Add multipliers based on number and diversity of tactics seen on the host\nfor (String tactic : tactic_ids) {\n multipliers.add("Tactic "+tactic);\n risk_multiplier *= 1 + params.tactic_base_multiplier * params.tactic_weights.getOrDefault(tactic, 0);\n}\n// Calculating final risk\ndouble final_risk = total_norm_risk;\nif (risk_multiplier > 1.0) {\n double prior_odds = (total_norm_risk) / (100 - total_norm_risk);\n double updated_odds = prior_odds * risk_multiplier; \n final_risk = 100 * updated_odds / (1 + updated_odds);\n}\n// Adding additional metadata\nList rule_stats = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n Map temp = new HashMap();\n temp["rule_name"] = key;\n temp["rule_risk"] = total_risk_stats[key][0];\n temp["rule_id"] = total_risk_stats[key][1];\n rule_stats.add(temp);\n}\n\nreturn ["calculated_score_norm": final_risk, "rule_risks": rule_stats, "multipliers": multipliers];';
|
||||
return {
|
||||
id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: stringifyScript ? JSON.stringify(source) : source,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegacyRiskUserCreateLevelScriptOptions = () => {
|
||||
const source =
|
||||
"double risk_score = (def)ctx.getByPath(params.risk_score);\nif (risk_score < 20) {\n ctx['risk'] = 'Unknown'\n}\nelse if (risk_score >= 20 && risk_score < 40) {\n ctx['risk'] = 'Low'\n}\nelse if (risk_score >= 40 && risk_score < 70) {\n ctx['risk'] = 'Moderate'\n}\nelse if (risk_score >= 70 && risk_score < 90) {\n ctx['risk'] = 'High'\n}\nelse if (risk_score >= 90) {\n ctx['risk'] = 'Critical'\n}";
|
||||
return {
|
||||
id: getLegacyRiskScoreLevelScriptId(RiskScoreEntity.user),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegacyRiskUserCreateMapScriptOptions = () => {
|
||||
const source =
|
||||
'// Get running sum of risk score per rule name per shard\\\\\nString rule_name = doc["signal.rule.name"].value;\ndef stats = state.rule_risk_stats.getOrDefault(rule_name, 0.0);\nstats = doc["signal.rule.risk_score"].value;\nstate.rule_risk_stats.put(rule_name, stats);';
|
||||
return {
|
||||
id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.user),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const getLegacyRiskUserCreateReduceScriptOptions = () => {
|
||||
const source =
|
||||
'// Consolidating time decayed risks from across all shards\nMap total_risk_stats = new HashMap();\nfor (state in states) {\n for (key in state.rule_risk_stats.keySet()) {\n def rule_stats = state.rule_risk_stats.get(key);\n def stats = total_risk_stats.getOrDefault(key, 0.0);\n stats = rule_stats;\n total_risk_stats.put(key, stats);\n }\n}\n// Consolidating individual rule risks and arranging them in decreasing order\nList risks = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n risks.add(total_risk_stats[key])\n}\nCollections.sort(risks, Collections.reverseOrder());\n// Calculating total risk and normalizing it to a range\ndouble total_risk = 0.0;\ndouble risk_cap = params.max_risk * params.zeta_constant;\nfor (int i=0;i<risks.length;i++) {\n total_risk += risks[i] / Math.pow((1+i), params.p);\n}\ndouble total_norm_risk = 100 * total_risk / risk_cap;\nif (total_norm_risk < 40) {\n total_norm_risk = 2.125 * total_norm_risk;\n}\nelse if (total_norm_risk >= 40 && total_norm_risk < 50) {\n total_norm_risk = 85 + (total_norm_risk - 40);\n}\nelse {\n total_norm_risk = 95 + (total_norm_risk - 50) / 10;\n}\n\nList rule_stats = new ArrayList();\nfor (key in total_risk_stats.keySet()) {\n Map temp = new HashMap();\n temp["rule_name"] = key;\n temp["rule_risk"] = total_risk_stats[key];\n rule_stats.add(temp);\n}\n\nreturn ["risk_score": total_norm_risk, "rule_risks": rule_stats];';
|
||||
return {
|
||||
id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user),
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source,
|
||||
},
|
||||
};
|
||||
};
|
|
@ -0,0 +1,307 @@
|
|||
/*
|
||||
* 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 { TRANSFORMS_URL } from '../../urls/risk_score';
|
||||
import { RiskScoreEntity } from './common';
|
||||
import { getLatestTransformIndex, getPivotTransformIndex } from './indices';
|
||||
import { getLegacyIngestPipelineName } from './ingest_pipelines';
|
||||
import {
|
||||
getLegacyRiskScoreInitScriptId,
|
||||
getLegacyRiskScoreMapScriptId,
|
||||
getLegacyRiskScoreReduceScriptId,
|
||||
} from './stored_scripts';
|
||||
|
||||
const DEFAULT_ALERTS_INDEX = '.alerts-security.alerts' as const;
|
||||
export const getAlertsIndex = (spaceId = 'default') => `${DEFAULT_ALERTS_INDEX}-${spaceId}`;
|
||||
|
||||
export const getRiskScorePivotTransformId = (
|
||||
riskScoreEntity: RiskScoreEntity,
|
||||
spaceId = 'default'
|
||||
) => `ml_${riskScoreEntity}riskscore_pivot_transform_${spaceId}`;
|
||||
|
||||
export const getRiskScoreLatestTransformId = (
|
||||
riskScoreEntity: RiskScoreEntity,
|
||||
spaceId = 'default'
|
||||
) => `ml_${riskScoreEntity}riskscore_latest_transform_${spaceId}`;
|
||||
|
||||
export const getTransformState = (transformId: string) => {
|
||||
return cy.request<{ transforms: Array<{ id: string; state: string }>; count: number }>({
|
||||
method: 'get',
|
||||
url: `${TRANSFORMS_URL}/transforms/${transformId}/_stats`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
});
|
||||
};
|
||||
|
||||
export const startTransforms = (transformIds: string[]) => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${TRANSFORMS_URL}/start_transforms`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
body: transformIds.map((id) => ({
|
||||
id,
|
||||
})),
|
||||
});
|
||||
};
|
||||
|
||||
const stopTransform = (state: {
|
||||
transforms: Array<{ id: string; state: string }>;
|
||||
count: number;
|
||||
}) => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${TRANSFORMS_URL}/stop_transforms`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
body:
|
||||
state != null && state.transforms.length > 0
|
||||
? [
|
||||
{
|
||||
id: state.transforms[0].id,
|
||||
state: state.transforms[0].state,
|
||||
},
|
||||
]
|
||||
: ([] as Array<{ id: string; state: string }>),
|
||||
});
|
||||
};
|
||||
|
||||
export const createTransform = (transformId: string, options: string | Record<string, unknown>) => {
|
||||
return cy.request({
|
||||
method: 'put',
|
||||
url: `${TRANSFORMS_URL}/transforms/${transformId}`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
body: options,
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteTransform = (transformId: string) => {
|
||||
return cy.request({
|
||||
method: 'post',
|
||||
url: `${TRANSFORMS_URL}/delete_transforms`,
|
||||
headers: { 'kbn-xsrf': 'cypress-creds-via-config' },
|
||||
failOnStatusCode: false,
|
||||
body: {
|
||||
transformsInfo: [
|
||||
{
|
||||
id: transformId,
|
||||
state: 'stopped',
|
||||
},
|
||||
],
|
||||
deleteDestIndex: true,
|
||||
deleteDestDataView: true,
|
||||
forceDelete: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
export const deleteTransforms = (transformIds: string[]) => {
|
||||
const deleteSingleTransform = (transformId: string) =>
|
||||
getTransformState(transformId)
|
||||
.then(({ body: result }) => {
|
||||
return stopTransform(result);
|
||||
})
|
||||
.then(() => {
|
||||
deleteTransform(transformId);
|
||||
});
|
||||
|
||||
transformIds.map((transformId) => deleteSingleTransform(transformId));
|
||||
};
|
||||
|
||||
export const getCreateLegacyMLHostPivotTransformOptions = ({
|
||||
spaceId = 'default',
|
||||
}: {
|
||||
spaceId?: string;
|
||||
}) => {
|
||||
const options = {
|
||||
dest: {
|
||||
index: getPivotTransformIndex(RiskScoreEntity.host, spaceId),
|
||||
pipeline: getLegacyIngestPipelineName(RiskScoreEntity.host),
|
||||
},
|
||||
frequency: '1h',
|
||||
pivot: {
|
||||
aggregations: {
|
||||
'@timestamp': {
|
||||
max: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
risk_stats: {
|
||||
scripted_metric: {
|
||||
combine_script: 'return state',
|
||||
init_script: {
|
||||
id: getLegacyRiskScoreInitScriptId(RiskScoreEntity.host),
|
||||
},
|
||||
map_script: {
|
||||
id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.host),
|
||||
},
|
||||
params: {
|
||||
lookback_time: 72,
|
||||
max_risk: 100,
|
||||
p: 1.5,
|
||||
server_multiplier: 1.5,
|
||||
tactic_base_multiplier: 0.25,
|
||||
tactic_weights: {
|
||||
TA0001: 1,
|
||||
TA0002: 2,
|
||||
TA0003: 3,
|
||||
TA0004: 4,
|
||||
TA0005: 4,
|
||||
TA0006: 4,
|
||||
TA0007: 4,
|
||||
TA0008: 5,
|
||||
TA0009: 6,
|
||||
TA0010: 7,
|
||||
TA0011: 6,
|
||||
TA0040: 8,
|
||||
TA0042: 1,
|
||||
TA0043: 1,
|
||||
},
|
||||
time_decay_constant: 6,
|
||||
zeta_constant: 2.612,
|
||||
},
|
||||
reduce_script: {
|
||||
id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.host),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group_by: {
|
||||
[`${RiskScoreEntity.host}.name`]: {
|
||||
terms: {
|
||||
field: `${RiskScoreEntity.host}.name`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
source: {
|
||||
index: [getAlertsIndex(spaceId)],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-5d',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
sync: {
|
||||
time: {
|
||||
delay: '120s',
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
return options;
|
||||
};
|
||||
|
||||
export const getCreateLegacyMLUserPivotTransformOptions = ({
|
||||
spaceId = 'default',
|
||||
}: {
|
||||
spaceId?: string;
|
||||
}) => {
|
||||
const options = {
|
||||
dest: {
|
||||
index: getPivotTransformIndex(RiskScoreEntity.user, spaceId),
|
||||
pipeline: getLegacyIngestPipelineName(RiskScoreEntity.user),
|
||||
},
|
||||
frequency: '1h',
|
||||
pivot: {
|
||||
aggregations: {
|
||||
'@timestamp': {
|
||||
max: {
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
risk_stats: {
|
||||
scripted_metric: {
|
||||
combine_script: 'return state',
|
||||
init_script: 'state.rule_risk_stats = new HashMap();',
|
||||
map_script: {
|
||||
id: getLegacyRiskScoreMapScriptId(RiskScoreEntity.user),
|
||||
},
|
||||
params: {
|
||||
max_risk: 100,
|
||||
p: 1.5,
|
||||
zeta_constant: 2.612,
|
||||
},
|
||||
reduce_script: {
|
||||
id: getLegacyRiskScoreReduceScriptId(RiskScoreEntity.user),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
group_by: {
|
||||
'user.name': {
|
||||
terms: {
|
||||
field: 'user.name',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
source: {
|
||||
index: [getAlertsIndex(spaceId)],
|
||||
query: {
|
||||
bool: {
|
||||
filter: [
|
||||
{
|
||||
range: {
|
||||
'@timestamp': {
|
||||
gte: 'now-90d',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
match: {
|
||||
'signal.status': 'open',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
sync: {
|
||||
time: {
|
||||
delay: '120s',
|
||||
field: '@timestamp',
|
||||
},
|
||||
},
|
||||
};
|
||||
return options;
|
||||
};
|
||||
|
||||
export const getCreateLegacyLatestTransformOptions = ({
|
||||
spaceId = 'default',
|
||||
riskScoreEntity,
|
||||
}: {
|
||||
spaceId?: string;
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
}) => {
|
||||
const options = {
|
||||
dest: {
|
||||
index: getLatestTransformIndex(riskScoreEntity, spaceId),
|
||||
},
|
||||
frequency: '1h',
|
||||
latest: {
|
||||
sort: '@timestamp',
|
||||
unique_key: [`${riskScoreEntity}.name`],
|
||||
},
|
||||
source: {
|
||||
index: [getPivotTransformIndex(riskScoreEntity, spaceId)],
|
||||
},
|
||||
sync: {
|
||||
time: {
|
||||
delay: '2s',
|
||||
field: 'ingest_timestamp',
|
||||
},
|
||||
},
|
||||
};
|
||||
return options;
|
||||
};
|
15
x-pack/plugins/security_solution/cypress/urls/risk_score.ts
Normal file
15
x-pack/plugins/security_solution/cypress/urls/risk_score.ts
Normal file
|
@ -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.
|
||||
*/
|
||||
|
||||
export const RISK_SCORE_URL = `/internal/risk_score` as const;
|
||||
export const INDICES_URL = `/internal/risk_score/indices` as const;
|
||||
export const INGEST_PIPELINES_URL = `/api/ingest_pipelines` as const;
|
||||
export const TRANSFORMS_URL = `/api/transform` as const;
|
||||
export const STORED_SCRIPTS_URL = `/internal/risk_score/stored_scripts` as const;
|
||||
export const RISK_SCORE_SAVED_OBJECTS_URL =
|
||||
`/internal/risk_score/prebuilt_content/saved_objects` as const;
|
||||
export const SAVED_OBJECTS_URL = `/api/saved_objects` as const;
|
|
@ -42,7 +42,7 @@ export const ENABLE_RISK_SCORE = (riskEntity: RiskScoreEntity) =>
|
|||
export const ENABLE_RISK_SCORE_DESCRIPTION = (riskEntity: RiskScoreEntity) =>
|
||||
i18n.translate('xpack.securitySolution.enableRiskScore.enableRiskScoreDescription', {
|
||||
defaultMessage:
|
||||
'Once you have enabled this feature you can get quick access to the {riskEntity} risk scores in this section.',
|
||||
'Once you have enabled this feature you can get quick access to the {riskEntity} risk scores in this section. The data might need an hour to be generated after enabling the module.',
|
||||
values: {
|
||||
riskEntity: getRiskEntityTranslation(riskEntity, true),
|
||||
},
|
||||
|
|
|
@ -28,10 +28,10 @@ const RiskScoreRestartButtonComponent = ({
|
|||
REQUEST_NAMES.REFRESH_RISK_SCORE,
|
||||
restartRiskScoreTransforms
|
||||
);
|
||||
|
||||
const spaceId = useSpaceId();
|
||||
|
||||
const { renderDocLink } = useRiskScoreToastContent(riskScoreEntity);
|
||||
const { http, notifications, theme } = useKibana().services;
|
||||
const { http, notifications } = useKibana().services;
|
||||
|
||||
const onClick = useCallback(async () => {
|
||||
fetch({
|
||||
|
@ -41,9 +41,8 @@ const RiskScoreRestartButtonComponent = ({
|
|||
renderDocLink,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
theme,
|
||||
});
|
||||
}, [fetch, http, notifications, refetch, renderDocLink, riskScoreEntity, spaceId, theme]);
|
||||
}, [fetch, http, notifications, refetch, renderDocLink, riskScoreEntity, spaceId]);
|
||||
|
||||
return (
|
||||
<EuiButton
|
||||
|
|
|
@ -30,14 +30,14 @@ export const USER_WARNING_TITLE = i18n.translate(
|
|||
export const HOST_WARNING_BODY = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.hostsDashboardWarningPanelBody',
|
||||
{
|
||||
defaultMessage: `We haven't detected any host risk score data from the hosts in your environment.`,
|
||||
defaultMessage: `We haven't detected any host risk score data from the hosts in your environment. The data might need an hour to be generated after enabling the module.`,
|
||||
}
|
||||
);
|
||||
|
||||
export const USER_WARNING_BODY = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.usersDashboardWarningPanelBody',
|
||||
{
|
||||
defaultMessage: `We haven't detected any user risk score data from the users in your environment.`,
|
||||
defaultMessage: `We haven't detected any user risk score data from the users in your environment. The data might need an hour to be generated after enabling the module.`,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -34,133 +34,40 @@ const mockTimerange = {
|
|||
to: 'endDate',
|
||||
};
|
||||
const mockRefetch = jest.fn();
|
||||
describe(`installRiskScoreModule - ${RiskScoreEntity.host}`, () => {
|
||||
beforeAll(async () => {
|
||||
await installRiskScoreModule({
|
||||
http: mockHttp,
|
||||
refetch: mockRefetch,
|
||||
spaceId: mockSpaceId,
|
||||
timerange: mockTimerange,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
describe.each([RiskScoreEntity.host, RiskScoreEntity.user])(
|
||||
`installRiskScoreModule - %s`,
|
||||
(riskScoreEntity) => {
|
||||
beforeAll(async () => {
|
||||
await installRiskScoreModule({
|
||||
http: mockHttp,
|
||||
refetch: mockRefetch,
|
||||
spaceId: mockSpaceId,
|
||||
timerange: mockTimerange,
|
||||
riskScoreEntity,
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_levels_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_init_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_map_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[2][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_reduce_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[3][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create IngestPipeline: ml_${RiskScoreEntity.host}riskscore_ingest_pipeline_${mockSpaceId}`, async () => {
|
||||
expect((api.createIngestPipeline as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.host}_risk_score_${mockSpaceId}`, async () => {
|
||||
expect((api.createIndices as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.host}_risk_score_latest_${mockSpaceId}`, async () => {
|
||||
expect((api.createIndices as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Transform: ml_${RiskScoreEntity.host}riskscore_pivot_transform_${mockSpaceId}`, async () => {
|
||||
expect((api.createTransform as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Transform: ml_${RiskScoreEntity.host}riskscore_latest_transform_${mockSpaceId}`, async () => {
|
||||
expect((api.createTransform as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Start Transforms`, () => {
|
||||
expect((api.startTransforms as jest.Mock).mock.calls[0][0].transformIds).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create ${RiskScoreEntity.host} dashboards`, () => {
|
||||
expect(
|
||||
(bulkCreatePrebuiltSavedObjects as jest.Mock).mock.calls[0][0].options.templateName
|
||||
).toEqual(`${RiskScoreEntity.host}RiskScoreDashboards`);
|
||||
});
|
||||
|
||||
it('Refresh module', () => {
|
||||
expect(mockRefetch).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`installRiskScoreModule - ${RiskScoreEntity.user}`, () => {
|
||||
beforeAll(async () => {
|
||||
await installRiskScoreModule({
|
||||
http: mockHttp,
|
||||
refetch: mockRefetch,
|
||||
spaceId: mockSpaceId,
|
||||
timerange: mockTimerange,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
it(`installRiskScore`, () => {
|
||||
expect((api.installRiskScore as jest.Mock).mock.calls[0][0].options.riskScoreEntity).toEqual(
|
||||
riskScoreEntity
|
||||
);
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.user}riskscore_levels_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
it(`Create ${riskScoreEntity} dashboards`, () => {
|
||||
expect(
|
||||
(bulkCreatePrebuiltSavedObjects as jest.Mock).mock.calls[0][0].options.templateName
|
||||
).toEqual(`${riskScoreEntity}RiskScoreDashboards`);
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.user}riskscore_map_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.user}riskscore_reduce_script_${mockSpaceId}`, async () => {
|
||||
expect((api.createStoredScript as jest.Mock).mock.calls[2][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create IngestPipeline: ml_${RiskScoreEntity.user}riskscore_ingest_pipeline_${mockSpaceId}`, async () => {
|
||||
expect((api.createIngestPipeline as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.user}_risk_score_${mockSpaceId}`, async () => {
|
||||
expect((api.createIndices as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.user}_risk_score_latest_${mockSpaceId}`, async () => {
|
||||
expect((api.createIndices as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Transform: ml_${RiskScoreEntity.user}riskscore_pivot_transform_${mockSpaceId}`, async () => {
|
||||
expect((api.createTransform as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Transform: ml_${RiskScoreEntity.user}riskscore_latest_transform_${mockSpaceId}`, async () => {
|
||||
expect((api.createTransform as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Start Transforms`, () => {
|
||||
expect((api.startTransforms as jest.Mock).mock.calls[0][0].transformIds).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Users dashboards`, () => {
|
||||
expect(
|
||||
(bulkCreatePrebuiltSavedObjects as jest.Mock).mock.calls[0][0].options.templateName
|
||||
).toEqual(`${RiskScoreEntity.user}RiskScoreDashboards`);
|
||||
});
|
||||
|
||||
it('Refresh module', () => {
|
||||
expect(mockRefetch).toBeCalled();
|
||||
});
|
||||
});
|
||||
it('Refresh module', () => {
|
||||
expect(mockRefetch).toBeCalled();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
describe.each([[RiskScoreEntity.host], [RiskScoreEntity.user]])(
|
||||
'uninstallLegacyRiskScoreModule - %s',
|
||||
|
@ -225,9 +132,9 @@ describe.each([[RiskScoreEntity.host], [RiskScoreEntity.user]])(
|
|||
beforeAll(async () => {
|
||||
await restartRiskScoreTransforms({
|
||||
http: mockHttp,
|
||||
spaceId: 'customSpace',
|
||||
refetch: mockRefetch,
|
||||
riskScoreEntity,
|
||||
spaceId: mockSpaceId,
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -11,28 +11,22 @@ import * as utils from '../../../../common/utils/risk_score_modules';
|
|||
import type { inputsModel } from '../../../common/store';
|
||||
|
||||
import {
|
||||
createIngestPipeline,
|
||||
createIndices,
|
||||
createStoredScript,
|
||||
createTransform,
|
||||
startTransforms,
|
||||
deleteStoredScripts,
|
||||
deleteTransforms,
|
||||
deleteIngestPipelines,
|
||||
stopTransforms,
|
||||
bulkCreatePrebuiltSavedObjects,
|
||||
bulkDeletePrebuiltSavedObjects,
|
||||
installRiskScore,
|
||||
bulkCreatePrebuiltSavedObjects,
|
||||
stopTransforms,
|
||||
startTransforms,
|
||||
} from '../../containers/onboarding/api';
|
||||
import {
|
||||
INGEST_PIPELINE_DELETION_ERROR_MESSAGE,
|
||||
INSTALLATION_ERROR,
|
||||
START_TRANSFORMS_ERROR_MESSAGE,
|
||||
TRANSFORM_CREATION_ERROR_MESSAGE,
|
||||
TRANSFORM_DELETION_ERROR_MESSAGE,
|
||||
UNINSTALLATION_ERROR,
|
||||
} from '../../containers/onboarding/api/translations';
|
||||
|
||||
interface InstallRiskyScoreModule {
|
||||
interface InstallRiskScoreModule {
|
||||
dashboard?: DashboardStart;
|
||||
http: HttpSetup;
|
||||
notifications?: NotificationsStart;
|
||||
|
@ -48,7 +42,7 @@ interface InstallRiskyScoreModule {
|
|||
};
|
||||
}
|
||||
|
||||
type UpgradeRiskyScoreModule = InstallRiskyScoreModule;
|
||||
type UpgradeRiskScoreModule = InstallRiskScoreModule;
|
||||
|
||||
const installHostRiskScoreModule = async ({
|
||||
dashboard,
|
||||
|
@ -57,157 +51,16 @@ const installHostRiskScoreModule = async ({
|
|||
refetch,
|
||||
renderDashboardLink,
|
||||
renderDocLink,
|
||||
spaceId = 'default',
|
||||
theme,
|
||||
timerange,
|
||||
}: InstallRiskyScoreModule) => {
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 1 Upload script: ml_hostriskscore_levels_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
}: InstallRiskScoreModule) => {
|
||||
await installRiskScore({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskHostCreateLevelScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 2 Upload script: ml_hostriskscore_init_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskHostCreateInitScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 3 Upload script: ml_hostriskscore_map_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskHostCreateMapScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 4 Upload script: ml_hostriskscore_reduce_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskHostCreateReduceScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 5 Upload the ingest pipeline: ml_hostriskscore_ingest_pipeline_{spaceId}
|
||||
*/
|
||||
await createIngestPipeline({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskScoreIngestPipelineOptions(RiskScoreEntity.host, spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 6 create ml_host_risk_score_{spaceId} index
|
||||
*/
|
||||
await createIndices({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getCreateRiskScoreIndicesOptions({
|
||||
spaceId,
|
||||
options: {
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 7 create transform: ml_hostriskscore_pivot_transform_{spaceId}
|
||||
*/
|
||||
await createTransform({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${INSTALLATION_ERROR} - ${TRANSFORM_CREATION_ERROR_MESSAGE}`,
|
||||
transformId: utils.getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId),
|
||||
options: utils.getCreateMLHostPivotTransformOptions({ spaceId }),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 9 create ml_host_risk_score_latest_{spaceId} index
|
||||
*/
|
||||
await createIndices({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getCreateRiskScoreLatestIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 10 create transform: ml_hostriskscore_latest_transform_{spaceId}
|
||||
*/
|
||||
await createTransform({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${INSTALLATION_ERROR} - ${TRANSFORM_CREATION_ERROR_MESSAGE}`,
|
||||
transformId: utils.getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId),
|
||||
options: utils.getCreateLatestTransformOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 8 Start the pivot transform
|
||||
* Step 11 Start the latest transform
|
||||
*/
|
||||
const transformIds = [
|
||||
utils.getRiskScorePivotTransformId(RiskScoreEntity.host, spaceId),
|
||||
utils.getRiskScoreLatestTransformId(RiskScoreEntity.host, spaceId),
|
||||
];
|
||||
await startTransforms({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${INSTALLATION_ERROR} - ${START_TRANSFORMS_ERROR_MESSAGE(transformIds.length)}`,
|
||||
transformIds,
|
||||
});
|
||||
|
||||
await restartRiskScoreTransforms({
|
||||
http,
|
||||
notifications,
|
||||
refetch,
|
||||
renderDocLink,
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
spaceId,
|
||||
theme,
|
||||
},
|
||||
});
|
||||
|
||||
// Install dashboards and relevant saved objects
|
||||
|
@ -239,144 +92,14 @@ const installUserRiskScoreModule = async ({
|
|||
spaceId = 'default',
|
||||
theme,
|
||||
timerange,
|
||||
}: InstallRiskyScoreModule) => {
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 1 Upload script: ml_userriskscore_levels_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
}: InstallRiskScoreModule) => {
|
||||
await installRiskScore({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskUserCreateLevelScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 2 Upload script: ml_userriskscore_map_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskUserCreateMapScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 3 Upload script: ml_userriskscore_reduce_script_{spaceId}
|
||||
*/
|
||||
await createStoredScript({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskUserCreateReduceScriptOptions(spaceId),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 4 Upload ingest pipeline: ml_userriskscore_ingest_pipeline_{spaceId}
|
||||
*/
|
||||
await createIngestPipeline({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getRiskScoreIngestPipelineOptions(RiskScoreEntity.user, spaceId),
|
||||
});
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 5 create ml_user_risk_score_{spaceId} index
|
||||
*/
|
||||
await createIndices({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getCreateRiskScoreIndicesOptions({
|
||||
spaceId,
|
||||
options: {
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 6 create Transform: ml_userriskscore_pivot_transform_{spaceId}
|
||||
*/
|
||||
await createTransform({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${INSTALLATION_ERROR} - ${TRANSFORM_CREATION_ERROR_MESSAGE}`,
|
||||
transformId: utils.getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId),
|
||||
options: utils.getCreateMLUserPivotTransformOptions({ spaceId }),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 8 create ml_user_risk_score_latest_{spaceId} index
|
||||
*/
|
||||
await createIndices({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
options: utils.getCreateRiskScoreLatestIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
}),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 9 create Transform: ml_userriskscore_latest_transform_{spaceId}
|
||||
*/
|
||||
await createTransform({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${INSTALLATION_ERROR} - ${TRANSFORM_CREATION_ERROR_MESSAGE}`,
|
||||
transformId: utils.getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId),
|
||||
options: utils.getCreateLatestTransformOptions({
|
||||
spaceId,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
}),
|
||||
});
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 7 Start the pivot transform
|
||||
* Step 10 Start the latest transform
|
||||
*/
|
||||
const transformIds = [
|
||||
utils.getRiskScorePivotTransformId(RiskScoreEntity.user, spaceId),
|
||||
utils.getRiskScoreLatestTransformId(RiskScoreEntity.user, spaceId),
|
||||
];
|
||||
await startTransforms({
|
||||
errorMessage: `${INSTALLATION_ERROR} - ${START_TRANSFORMS_ERROR_MESSAGE(transformIds.length)}`,
|
||||
http,
|
||||
notifications,
|
||||
renderDocLink,
|
||||
theme,
|
||||
transformIds,
|
||||
});
|
||||
|
||||
/**
|
||||
* Restart transform immediately to force it pick up the alerts data.
|
||||
* This can effectively reduce the chance of no data appears once installation complete.
|
||||
* */
|
||||
await restartRiskScoreTransforms({
|
||||
http,
|
||||
notifications,
|
||||
refetch,
|
||||
renderDocLink,
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
spaceId,
|
||||
theme,
|
||||
},
|
||||
});
|
||||
|
||||
// Install dashboards and relevant saved objects
|
||||
|
@ -398,7 +121,7 @@ const installUserRiskScoreModule = async ({
|
|||
}
|
||||
};
|
||||
|
||||
export const installRiskScoreModule = async (settings: InstallRiskyScoreModule) => {
|
||||
export const installRiskScoreModule = async (settings: InstallRiskScoreModule) => {
|
||||
if (settings.riskScoreEntity === RiskScoreEntity.user) {
|
||||
await installUserRiskScoreModule(settings);
|
||||
} else {
|
||||
|
@ -442,68 +165,67 @@ export const uninstallLegacyRiskScoreModule = async ({
|
|||
|
||||
const legacyIngestPipelineNames = [utils.getLegacyIngestPipelineName(riskScoreEntity)];
|
||||
|
||||
/**
|
||||
* Intended not to pass notification to bulkDeletePrebuiltSavedObjects.
|
||||
* As the only error it can happen is saved object not found, and
|
||||
* that is what bulkDeletePrebuiltSavedObjects wants.
|
||||
* (Before 8.5 once an saved object was created, it was shared across different spaces.
|
||||
* If it has been upgrade in one space, "saved object not found" will happen when upgrading other spaces.
|
||||
* Or it could be users manually deleted the saved object.)
|
||||
*/
|
||||
await bulkDeletePrebuiltSavedObjects({
|
||||
http,
|
||||
options: {
|
||||
templateName: `${riskScoreEntity}RiskScoreDashboards`,
|
||||
},
|
||||
});
|
||||
|
||||
await deleteTransforms({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${UNINSTALLATION_ERROR} - ${TRANSFORM_DELETION_ERROR_MESSAGE(
|
||||
legacyTransformIds.length
|
||||
)}`,
|
||||
transformIds: legacyTransformIds,
|
||||
options: {
|
||||
deleteDestIndex: true,
|
||||
deleteDestDataView: true,
|
||||
forceDelete: false,
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Intended not to pass notification to deleteIngestPipelines.
|
||||
* As the only error it can happen is ingest pipeline not found, and
|
||||
* that is what deleteIngestPipelines wants.
|
||||
* (Before 8.5 once an ingest pipeline was created, it was shared across different spaces.
|
||||
* If it has been upgrade in one space, "ingest pipeline not found" will happen when upgrading other spaces.
|
||||
* Or it could be users manually deleted the ingest pipeline.)
|
||||
*/
|
||||
await deleteIngestPipelines({
|
||||
http,
|
||||
errorMessage: `${UNINSTALLATION_ERROR} - ${INGEST_PIPELINE_DELETION_ERROR_MESSAGE(
|
||||
legacyIngestPipelineNames.length
|
||||
)}`,
|
||||
names: legacyIngestPipelineNames.join(','),
|
||||
});
|
||||
|
||||
/**
|
||||
* Intended not to pass notification to deleteStoredScripts.
|
||||
* As the only error it can happen is script not found, and
|
||||
* that is what deleteStoredScripts wants.
|
||||
* (Before 8.5 once a script was created, it was shared across different spaces.
|
||||
* If it has been upgrade in one space, "script not found" will happen when upgrading other spaces.
|
||||
* Or it could be users manually deleted the script.)
|
||||
*/
|
||||
await deleteStoredScripts({
|
||||
http,
|
||||
ids:
|
||||
riskScoreEntity === RiskScoreEntity.user
|
||||
? legacyRiskScoreUsersScriptIds
|
||||
: legacyRiskScoreHostsScriptIds,
|
||||
});
|
||||
await Promise.all([
|
||||
/**
|
||||
* Intended not to pass notification to bulkDeletePrebuiltSavedObjects.
|
||||
* As the only error it can happen is saved object not found, and
|
||||
* that is what bulkDeletePrebuiltSavedObjects wants.
|
||||
* (Before 8.5 once an saved object was created, it was shared across different spaces.
|
||||
* If it has been upgrade in one space, "saved object not found" will happen when upgrading other spaces.
|
||||
* Or it could be users manually deleted the saved object.)
|
||||
*/
|
||||
bulkDeletePrebuiltSavedObjects({
|
||||
http,
|
||||
options: {
|
||||
templateName: `${riskScoreEntity}RiskScoreDashboards`,
|
||||
},
|
||||
}),
|
||||
deleteTransforms({
|
||||
http,
|
||||
theme,
|
||||
renderDocLink,
|
||||
notifications,
|
||||
errorMessage: `${UNINSTALLATION_ERROR} - ${TRANSFORM_DELETION_ERROR_MESSAGE(
|
||||
legacyTransformIds.length
|
||||
)}`,
|
||||
transformIds: legacyTransformIds,
|
||||
options: {
|
||||
deleteDestIndex: true,
|
||||
deleteDestDataView: true,
|
||||
forceDelete: false,
|
||||
},
|
||||
}),
|
||||
/**
|
||||
* Intended not to pass notification to deleteIngestPipelines.
|
||||
* As the only error it can happen is ingest pipeline not found, and
|
||||
* that is what deleteIngestPipelines wants.
|
||||
* (Before 8.5 once an ingest pipeline was created, it was shared across different spaces.
|
||||
* If it has been upgrade in one space, "ingest pipeline not found" will happen when upgrading other spaces.
|
||||
* Or it could be users manually deleted the ingest pipeline.)
|
||||
*/
|
||||
deleteIngestPipelines({
|
||||
http,
|
||||
errorMessage: `${UNINSTALLATION_ERROR} - ${INGEST_PIPELINE_DELETION_ERROR_MESSAGE(
|
||||
legacyIngestPipelineNames.length
|
||||
)}`,
|
||||
names: legacyIngestPipelineNames.join(','),
|
||||
}),
|
||||
/**
|
||||
* Intended not to pass notification to deleteStoredScripts.
|
||||
* As the only error it can happen is script not found, and
|
||||
* that is what deleteStoredScripts wants.
|
||||
* (Before 8.5 once a script was created, it was shared across different spaces.
|
||||
* If it has been upgrade in one space, "script not found" will happen when upgrading other spaces.
|
||||
* Or it could be users manually deleted the script.)
|
||||
*/
|
||||
deleteStoredScripts({
|
||||
http,
|
||||
ids:
|
||||
riskScoreEntity === RiskScoreEntity.user
|
||||
? legacyRiskScoreUsersScriptIds
|
||||
: legacyRiskScoreHostsScriptIds,
|
||||
}),
|
||||
]);
|
||||
|
||||
if (refetch) {
|
||||
refetch();
|
||||
|
@ -520,7 +242,7 @@ export const upgradeHostRiskScoreModule = async ({
|
|||
spaceId = 'default',
|
||||
theme,
|
||||
timerange,
|
||||
}: UpgradeRiskyScoreModule) => {
|
||||
}: UpgradeRiskScoreModule) => {
|
||||
await uninstallLegacyRiskScoreModule({
|
||||
http,
|
||||
notifications,
|
||||
|
@ -553,7 +275,7 @@ export const upgradeUserRiskScoreModule = async ({
|
|||
spaceId = 'default',
|
||||
theme,
|
||||
timerange,
|
||||
}: UpgradeRiskyScoreModule) => {
|
||||
}: UpgradeRiskScoreModule) => {
|
||||
await uninstallLegacyRiskScoreModule({
|
||||
http,
|
||||
notifications,
|
||||
|
@ -583,7 +305,6 @@ export const restartRiskScoreTransforms = async ({
|
|||
renderDocLink,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
theme,
|
||||
}: {
|
||||
http: HttpSetup;
|
||||
notifications?: NotificationsStart;
|
||||
|
@ -591,7 +312,6 @@ export const restartRiskScoreTransforms = async ({
|
|||
renderDocLink?: (message: string) => React.ReactNode;
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
spaceId?: string;
|
||||
theme?: ThemeServiceStart;
|
||||
}) => {
|
||||
const transformIds = [
|
||||
utils.getRiskScorePivotTransformId(riskScoreEntity, spaceId),
|
||||
|
@ -602,7 +322,6 @@ export const restartRiskScoreTransforms = async ({
|
|||
http,
|
||||
notifications,
|
||||
renderDocLink,
|
||||
theme,
|
||||
transformIds,
|
||||
});
|
||||
|
||||
|
@ -610,7 +329,6 @@ export const restartRiskScoreTransforms = async ({
|
|||
http,
|
||||
notifications,
|
||||
renderDocLink,
|
||||
theme,
|
||||
transformIds,
|
||||
});
|
||||
|
||||
|
|
|
@ -9,3 +9,4 @@ export * from './ingest_pipelines';
|
|||
export * from './transforms';
|
||||
export * from './stored_scripts';
|
||||
export * from './saved_objects';
|
||||
export * from './onboarding';
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
/*
|
||||
* 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 { HttpSetup, NotificationsStart } from '@kbn/core/public';
|
||||
|
||||
import { INTERNAL_RISK_SCORE_URL } from '../../../../../common/constants';
|
||||
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import {
|
||||
HOST_RISK_SCORES_ENABLED_TITLE,
|
||||
INSTALLATION_ERROR,
|
||||
RISK_SCORES_ENABLED_TEXT,
|
||||
USER_RISK_SCORES_ENABLED_TITLE,
|
||||
} from './translations';
|
||||
|
||||
interface Options {
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
}
|
||||
|
||||
type Response = Record<string, { success?: boolean; error?: Error }>;
|
||||
const toastLifeTimeMs = 600000;
|
||||
|
||||
export const installRiskScore = ({
|
||||
errorMessage,
|
||||
http,
|
||||
notifications,
|
||||
options,
|
||||
renderDocLink,
|
||||
signal,
|
||||
}: {
|
||||
errorMessage?: string;
|
||||
http: HttpSetup;
|
||||
notifications?: NotificationsStart;
|
||||
options: Options;
|
||||
renderDocLink?: (message: string) => React.ReactNode;
|
||||
signal?: AbortSignal;
|
||||
}) => {
|
||||
return http
|
||||
.post<Response[]>(INTERNAL_RISK_SCORE_URL, {
|
||||
body: JSON.stringify(options),
|
||||
signal,
|
||||
})
|
||||
.then((result) => {
|
||||
const resp = result.reduce(
|
||||
(acc, curr) => {
|
||||
const [[key, res]] = Object.entries(curr);
|
||||
if (res.success) {
|
||||
return res.success != null ? { ...acc, success: [...acc.success, `${key}`] } : acc;
|
||||
} else {
|
||||
return res.error != null
|
||||
? { ...acc, error: [...acc.error, `${key}: ${res?.error?.message}`] }
|
||||
: acc;
|
||||
}
|
||||
},
|
||||
{ success: [] as string[], error: [] as string[] }
|
||||
);
|
||||
|
||||
if (resp.error.length > 0) {
|
||||
notifications?.toasts?.addError(new Error(errorMessage ?? INSTALLATION_ERROR), {
|
||||
title: errorMessage ?? INSTALLATION_ERROR,
|
||||
toastMessage: renderDocLink
|
||||
? (renderDocLink(resp.error.join(', ')) as unknown as string)
|
||||
: resp.error.join(', '),
|
||||
toastLifeTimeMs,
|
||||
});
|
||||
} else {
|
||||
notifications?.toasts?.addSuccess({
|
||||
'data-test-subj': `${options.riskScoreEntity}EnableSuccessToast`,
|
||||
title:
|
||||
options.riskScoreEntity === RiskScoreEntity.user
|
||||
? USER_RISK_SCORES_ENABLED_TITLE
|
||||
: HOST_RISK_SCORES_ENABLED_TITLE,
|
||||
text: RISK_SCORES_ENABLED_TEXT(resp.success.join(', ')),
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
notifications?.toasts?.addError(new Error(errorMessage ?? INSTALLATION_ERROR), {
|
||||
title: errorMessage ?? INSTALLATION_ERROR,
|
||||
toastMessage: renderDocLink ? renderDocLink(e?.body?.message) : e?.body?.message,
|
||||
toastLifeTimeMs,
|
||||
});
|
||||
});
|
||||
};
|
|
@ -5,13 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type {
|
||||
HttpSetup,
|
||||
NotificationsStart,
|
||||
SavedObject,
|
||||
SavedObjectAttributes,
|
||||
ThemeServiceStart,
|
||||
} from '@kbn/core/public';
|
||||
import type { HttpSetup, NotificationsStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import { toMountPoint } from '@kbn/kibana-react-plugin/public';
|
||||
import type { DashboardStart } from '@kbn/dashboard-plugin/public';
|
||||
import { RISKY_HOSTS_DASHBOARD_TITLE, RISKY_USERS_DASHBOARD_TITLE } from '../../../constants';
|
||||
|
@ -30,8 +24,10 @@ import {
|
|||
|
||||
const toastLifeTimeMs = 600000;
|
||||
|
||||
type DashboardsSavedObjectTemplate = `${RiskScoreEntity}RiskScoreDashboards`;
|
||||
|
||||
interface Options {
|
||||
templateName: string;
|
||||
templateName: DashboardsSavedObjectTemplate;
|
||||
}
|
||||
|
||||
export const bulkCreatePrebuiltSavedObjects = async ({
|
||||
|
@ -58,20 +54,24 @@ export const bulkCreatePrebuiltSavedObjects = async ({
|
|||
theme?: ThemeServiceStart;
|
||||
}) => {
|
||||
const res = await http
|
||||
.post<{ saved_objects: Array<SavedObject<SavedObjectAttributes>> }>(
|
||||
prebuiltSavedObjectsBulkCreateUrl(options.templateName)
|
||||
)
|
||||
.post<
|
||||
Record<
|
||||
DashboardsSavedObjectTemplate,
|
||||
{
|
||||
success?: boolean;
|
||||
error: Error;
|
||||
body?: Array<{ type: string; title: string; id: string; name: string }>;
|
||||
}
|
||||
>
|
||||
>(prebuiltSavedObjectsBulkCreateUrl(options.templateName))
|
||||
.then((result) => {
|
||||
const errors = result.saved_objects.reduce<string[]>((acc, o) => {
|
||||
return o.error != null ? [...acc, `${o.id}: ${o.error.message}`] : acc;
|
||||
}, []);
|
||||
const response = result[options.templateName];
|
||||
const error = response?.error?.message;
|
||||
|
||||
if (errors.length > 0) {
|
||||
if (error) {
|
||||
notifications?.toasts?.addError(new Error(errorMessage ?? IMPORT_SAVED_OBJECTS_FAILURE), {
|
||||
title: errorMessage ?? IMPORT_SAVED_OBJECTS_FAILURE,
|
||||
toastMessage: renderDocLink
|
||||
? (renderDocLink(errors.join(', ')) as unknown as string)
|
||||
: errors.join(', '),
|
||||
toastMessage: renderDocLink ? (renderDocLink(error) as unknown as string) : error,
|
||||
toastLifeTimeMs,
|
||||
});
|
||||
} else {
|
||||
|
@ -80,8 +80,8 @@ export const bulkCreatePrebuiltSavedObjects = async ({
|
|||
? RISKY_USERS_DASHBOARD_TITLE
|
||||
: RISKY_HOSTS_DASHBOARD_TITLE;
|
||||
|
||||
const targetDashboard = result.saved_objects.find(
|
||||
(obj) => obj.type === 'dashboard' && obj?.attributes?.title === dashboardTitle
|
||||
const targetDashboard = response?.body?.find(
|
||||
(obj) => obj.type === 'dashboard' && obj?.title === dashboardTitle
|
||||
);
|
||||
|
||||
let targetUrl;
|
||||
|
@ -95,12 +95,15 @@ export const bulkCreatePrebuiltSavedObjects = async ({
|
|||
});
|
||||
}
|
||||
|
||||
const successMessage = result.saved_objects
|
||||
.map((o) => o?.attributes?.title ?? o?.attributes?.name)
|
||||
.join(', ');
|
||||
const successMessage = response?.body?.map((o) => o?.title ?? o?.name).join(', ');
|
||||
|
||||
if (successMessage == null || response?.body?.length == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
notifications?.toasts?.addSuccess({
|
||||
title: IMPORT_SAVED_OBJECTS_SUCCESS(result.saved_objects.length),
|
||||
'data-test-subj': `${options.templateName}SuccessToast`,
|
||||
title: IMPORT_SAVED_OBJECTS_SUCCESS(response?.body?.length),
|
||||
text: toMountPoint(
|
||||
renderDashboardLink && targetUrl
|
||||
? renderDashboardLink(successMessage, targetUrl)
|
||||
|
@ -109,6 +112,7 @@ export const bulkCreatePrebuiltSavedObjects = async ({
|
|||
theme$: theme?.theme$,
|
||||
}
|
||||
),
|
||||
toastLifeTimeMs,
|
||||
});
|
||||
}
|
||||
})
|
||||
|
|
|
@ -5,9 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { RISK_SCORE_RESTART_TRANSFORMS } from '../../../../../common/constants';
|
||||
import {
|
||||
GET_TRANSFORM_STATE_ERROR_MESSAGE,
|
||||
GET_TRANSFORM_STATE_NOT_FOUND_MESSAGE,
|
||||
RESTART_TRANSFORMS_ERROR_MESSAGE,
|
||||
START_TRANSFORMS_ERROR_MESSAGE,
|
||||
STOP_TRANSFORMS_ERROR_MESSAGE,
|
||||
TRANSFORM_CREATION_ERROR_MESSAGE,
|
||||
|
@ -20,6 +22,8 @@ import type {
|
|||
DeleteTransformsResult,
|
||||
GetTransformsState,
|
||||
GetTransformState,
|
||||
RestartTransforms,
|
||||
RestartTransformResult,
|
||||
StartTransforms,
|
||||
StartTransformsResult,
|
||||
StopTransforms,
|
||||
|
@ -318,3 +322,49 @@ export async function deleteTransforms({
|
|||
|
||||
return res;
|
||||
}
|
||||
|
||||
export async function restartTransforms({
|
||||
http,
|
||||
notifications,
|
||||
renderDocLink,
|
||||
signal,
|
||||
errorMessage,
|
||||
riskScoreEntity,
|
||||
}: RestartTransforms) {
|
||||
const res = await http
|
||||
.post<RestartTransformResult[]>(`${RISK_SCORE_RESTART_TRANSFORMS}`, {
|
||||
body: JSON.stringify({ riskScoreEntity }),
|
||||
signal,
|
||||
})
|
||||
.then((result) => {
|
||||
const failedIds = result.reduce<string[]>((acc, curr) => {
|
||||
const [[key, val]] = Object.entries(curr);
|
||||
return !val.success
|
||||
? [...acc, val?.error?.message ? `${key}: ${val?.error?.message}` : key]
|
||||
: acc;
|
||||
}, []);
|
||||
const errorMessageTitle = errorMessage ?? RESTART_TRANSFORMS_ERROR_MESSAGE(failedIds.length);
|
||||
|
||||
if (failedIds.length > 0) {
|
||||
notifications?.toasts?.addError(new Error(errorMessageTitle), {
|
||||
title: errorMessageTitle,
|
||||
toastMessage: getErrorToastMessage({
|
||||
messageBody: failedIds.join(', '),
|
||||
renderDocLink,
|
||||
}),
|
||||
toastLifeTimeMs,
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
})
|
||||
.catch((e) => {
|
||||
notifications?.toasts?.addError(e, {
|
||||
title: errorMessage ?? RESTART_TRANSFORMS_ERROR_MESSAGE(),
|
||||
toastMessage: getErrorToastMessage({ messageBody: e?.body?.message, renderDocLink }),
|
||||
toastLifeTimeMs,
|
||||
});
|
||||
});
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -80,6 +80,12 @@ export const START_TRANSFORMS_ERROR_MESSAGE = (totalCount: number) =>
|
|||
defaultMessage: `Failed to start {totalCount, plural, =1 {Transform} other {Transforms}}`,
|
||||
});
|
||||
|
||||
export const RESTART_TRANSFORMS_ERROR_MESSAGE = (totalCount?: number) =>
|
||||
i18n.translate('xpack.securitySolution.riskScore.api.transforms.start.errorMessageTitle', {
|
||||
values: { totalCount },
|
||||
defaultMessage: `Failed to start {totalCount, plural, =1 {Transform} other {Transforms}}`,
|
||||
});
|
||||
|
||||
export const STOP_TRANSFORMS_ERROR_MESSAGE = (totalCount: number) =>
|
||||
i18n.translate('xpack.securitySolution.riskScore.api.transforms.stop.errorMessageTitle', {
|
||||
values: { totalCount },
|
||||
|
@ -119,3 +125,23 @@ export const DELETE_SAVED_OBJECTS_FAILURE = i18n.translate(
|
|||
defaultMessage: `Failed to delete saved objects`,
|
||||
}
|
||||
);
|
||||
|
||||
export const HOST_RISK_SCORES_ENABLED_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.hostRiskScoresEnabledTitle',
|
||||
{
|
||||
defaultMessage: `Host Risk Scores enabled`,
|
||||
}
|
||||
);
|
||||
|
||||
export const USER_RISK_SCORES_ENABLED_TITLE = i18n.translate(
|
||||
'xpack.securitySolution.riskScore.userRiskScoresEnabledTitle',
|
||||
{
|
||||
defaultMessage: `User Risk Scores enabled`,
|
||||
}
|
||||
);
|
||||
|
||||
export const RISK_SCORES_ENABLED_TEXT = (items: string) =>
|
||||
i18n.translate('xpack.securitySolution.riskScore.savedObjects.enableRiskScoreSuccessTitle', {
|
||||
values: { items },
|
||||
defaultMessage: `{items} imported successfully`,
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type { HttpSetup, NotificationsStart, ThemeServiceStart } from '@kbn/core/public';
|
||||
import type { OutputError } from '@kbn/securitysolution-es-utils';
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy/security_solution/risk_score/common';
|
||||
|
||||
interface RiskyScoreApiBase {
|
||||
errorMessage?: string;
|
||||
|
@ -71,6 +73,10 @@ export interface StartTransforms extends RiskyScoreApiBase {
|
|||
transformIds: string[];
|
||||
}
|
||||
|
||||
export interface RestartTransforms extends RiskyScoreApiBase {
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
}
|
||||
|
||||
interface TransformResult {
|
||||
success: boolean;
|
||||
error?: { root_cause?: unknown; type?: string; reason?: string };
|
||||
|
@ -78,6 +84,11 @@ interface TransformResult {
|
|||
|
||||
export type StartTransformsResult = Record<string, TransformResult>;
|
||||
|
||||
export type RestartTransformResult = Record<
|
||||
string,
|
||||
{ success: boolean; error: OutputError | null }
|
||||
>;
|
||||
|
||||
export interface StopTransforms extends RiskyScoreApiBase {
|
||||
transformIds: string[];
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import {
|
||||
serverMock,
|
||||
requestContextMock,
|
||||
|
@ -19,35 +19,37 @@ import {
|
|||
import { createEsIndexRoute } from './create_index_route';
|
||||
import { RISK_SCORE_CREATE_INDEX } from '../../../../common/constants';
|
||||
import { createIndex } from './lib/create_index';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
const testIndex = 'test-index';
|
||||
|
||||
jest.mock('./lib/create_index', () => {
|
||||
const actualModule = jest.requireActual('./lib/create_index');
|
||||
return {
|
||||
...actualModule,
|
||||
createIndex: jest.fn(),
|
||||
createIndex: jest.fn().mockResolvedValue({ [testIndex]: { success: true, error: null } }),
|
||||
};
|
||||
});
|
||||
|
||||
describe('createEsIndexRoute', () => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { context } = requestContextMock.createTools();
|
||||
|
||||
const logger = { error: jest.fn() } as unknown as Logger;
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
|
||||
server = serverMock.create();
|
||||
({ context } = requestContextMock.createTools());
|
||||
|
||||
createEsIndexRoute(server.router);
|
||||
createEsIndexRoute(server.router, logger);
|
||||
});
|
||||
|
||||
it('create index', async () => {
|
||||
const request = requestMock.create({
|
||||
method: 'put',
|
||||
path: RISK_SCORE_CREATE_INDEX,
|
||||
body: { index: 'test-index', mappings: {} },
|
||||
body: { index: testIndex, mappings: {} },
|
||||
});
|
||||
|
||||
const response = await server.inject(request, requestContextMock.convertContext(context));
|
||||
expect(createIndex).toHaveBeenCalled();
|
||||
expect(response.status).toEqual(200);
|
||||
|
@ -63,4 +65,20 @@ describe('createEsIndexRoute', () => {
|
|||
|
||||
expect(result.ok).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('return error if failed to create index', async () => {
|
||||
(createIndex as jest.Mock).mockResolvedValue({
|
||||
[testIndex]: { success: false, error: transformError(new Error('unknown error')) },
|
||||
});
|
||||
const request = requestMock.create({
|
||||
method: 'put',
|
||||
path: RISK_SCORE_CREATE_INDEX,
|
||||
body: { index: testIndex, mappings: {} },
|
||||
});
|
||||
|
||||
const response = await server.inject(request, requestContextMock.convertContext(context));
|
||||
expect(createIndex).toHaveBeenCalled();
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({ message: 'unknown error', status_code: 500 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,13 +5,15 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { RISK_SCORE_CREATE_INDEX } from '../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../types';
|
||||
import { createEsIndexBodySchema, createIndex } from './lib/create_index';
|
||||
|
||||
export const createEsIndexRoute = (router: SecuritySolutionPluginRouter) => {
|
||||
export const createEsIndexRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
|
||||
router.put(
|
||||
{
|
||||
path: RISK_SCORE_CREATE_INDEX,
|
||||
|
@ -23,15 +25,27 @@ export const createEsIndexRoute = (router: SecuritySolutionPluginRouter) => {
|
|||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const esClient = client.asCurrentUser;
|
||||
const options = request.body;
|
||||
|
||||
try {
|
||||
await createIndex({
|
||||
client,
|
||||
const result = await createIndex({
|
||||
esClient,
|
||||
logger,
|
||||
options,
|
||||
});
|
||||
return response.ok({ body: options });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
const error = result[options.index].error;
|
||||
|
||||
if (error != null) {
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
} else {
|
||||
return response.ok({ body: options });
|
||||
}
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
|
|
|
@ -6,21 +6,38 @@
|
|||
*/
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
|
||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
|
||||
export const createEsIndexBodySchema = schema.object({
|
||||
index: schema.string({ minLength: 1 }),
|
||||
mappings: schema.maybe(schema.recordOf(schema.string({ minLength: 1 }), schema.any())),
|
||||
mappings: schema.maybe(
|
||||
schema.oneOf([schema.string(), schema.recordOf(schema.string({ minLength: 1 }), schema.any())])
|
||||
),
|
||||
});
|
||||
|
||||
type CreateEsIndexBodySchema = TypeOf<typeof createEsIndexBodySchema>;
|
||||
|
||||
export const createIndex = async ({
|
||||
client,
|
||||
esClient,
|
||||
logger,
|
||||
options,
|
||||
}: {
|
||||
client: IScopedClusterClient;
|
||||
esClient: ElasticsearchClient;
|
||||
logger: Logger;
|
||||
options: CreateEsIndexBodySchema;
|
||||
}) => {
|
||||
await client.asCurrentUser.indices.create(options);
|
||||
try {
|
||||
await esClient.indices.create({
|
||||
index: options.index,
|
||||
mappings:
|
||||
typeof options.mappings === 'string' ? JSON.parse(options.mappings) : options.mappings,
|
||||
});
|
||||
return { [options.index]: { success: true, error: null } };
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to create index: ${options.index}: ${error.message}`);
|
||||
|
||||
return { [options.index]: { success: false, error } };
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import type { Pipeline } from '../../../../../common/types/risk_scores';
|
||||
|
||||
export const createIngestPipeline = async ({
|
||||
esClient,
|
||||
logger,
|
||||
options,
|
||||
}: {
|
||||
esClient: ElasticsearchClient;
|
||||
logger: Logger;
|
||||
options: Pipeline;
|
||||
}) => {
|
||||
const processors =
|
||||
typeof options.processors === 'string' ? JSON.parse(options.processors) : options.processors;
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
const { name, description, version, on_failure } = options;
|
||||
|
||||
try {
|
||||
await esClient.ingest.putPipeline({
|
||||
id: name,
|
||||
body: {
|
||||
description,
|
||||
processors,
|
||||
version,
|
||||
on_failure,
|
||||
},
|
||||
});
|
||||
|
||||
return { [name]: { success: true, error: null } };
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
logger.error(`Failed to create ingest pipeline: ${name}: ${error.message}`);
|
||||
|
||||
return { [name]: { success: false, error } };
|
||||
}
|
||||
};
|
|
@ -0,0 +1,313 @@
|
|||
/*
|
||||
* 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 { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import {
|
||||
getCreateLatestTransformOptions,
|
||||
getCreateMLHostPivotTransformOptions,
|
||||
getCreateMLUserPivotTransformOptions,
|
||||
getCreateRiskScoreIndicesOptions,
|
||||
getCreateRiskScoreLatestIndicesOptions,
|
||||
getRiskHostCreateInitScriptOptions,
|
||||
getRiskHostCreateLevelScriptOptions,
|
||||
getRiskHostCreateMapScriptOptions,
|
||||
getRiskHostCreateReduceScriptOptions,
|
||||
getRiskScoreIngestPipelineOptions,
|
||||
getRiskScoreLatestTransformId,
|
||||
getRiskScorePivotTransformId,
|
||||
getRiskUserCreateLevelScriptOptions,
|
||||
getRiskUserCreateMapScriptOptions,
|
||||
getRiskUserCreateReduceScriptOptions,
|
||||
} from '../../../../../common/utils/risk_score_modules';
|
||||
import { createIndex } from '../../indices/lib/create_index';
|
||||
import { createStoredScript } from '../../stored_scripts/lib/create_script';
|
||||
import { createAndStartTransform } from '../../transform/helpers/transforms';
|
||||
import { createIngestPipeline } from './ingest_pipeline';
|
||||
|
||||
interface InstallRiskScoreModule {
|
||||
esClient: ElasticsearchClient;
|
||||
logger: Logger;
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
spaceId: string;
|
||||
}
|
||||
|
||||
const createHostRiskScoreIngestPipelineGrouping = ({
|
||||
esClient,
|
||||
logger,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
}: InstallRiskScoreModule) => {
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 1 Upload script: ml_hostriskscore_levels_script_{spaceId}
|
||||
*/
|
||||
const createLevelScriptOptions = getRiskHostCreateLevelScriptOptions(spaceId);
|
||||
|
||||
return createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: createLevelScriptOptions,
|
||||
}).then((createStoredScriptResult) => {
|
||||
if (createStoredScriptResult[createLevelScriptOptions.id].success) {
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 5 Upload the ingest pipeline: ml_hostriskscore_ingest_pipeline_{spaceId}
|
||||
*/
|
||||
const createIngestPipelineOptions = getRiskScoreIngestPipelineOptions(
|
||||
riskScoreEntity,
|
||||
spaceId
|
||||
);
|
||||
return createIngestPipeline({
|
||||
esClient,
|
||||
logger,
|
||||
options: createIngestPipelineOptions,
|
||||
}).then((createIngestPipelineResult) => {
|
||||
return [createStoredScriptResult, createIngestPipelineResult];
|
||||
});
|
||||
} else {
|
||||
return [createStoredScriptResult];
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const installHostRiskScoreModule = async ({
|
||||
esClient,
|
||||
riskScoreEntity,
|
||||
logger,
|
||||
spaceId,
|
||||
}: InstallRiskScoreModule) => {
|
||||
const result = await Promise.all([
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 1 Upload script: ml_hostriskscore_levels_script_{spaceId}
|
||||
* Step 5 Upload the ingest pipeline: ml_hostriskscore_ingest_pipeline_{spaceId}
|
||||
*/
|
||||
createHostRiskScoreIngestPipelineGrouping({
|
||||
esClient,
|
||||
logger,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
}),
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 2 Upload script: ml_hostriskscore_init_script_{spaceId}
|
||||
*/
|
||||
createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: getRiskHostCreateInitScriptOptions(spaceId),
|
||||
}),
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 3 Upload script: ml_hostriskscore_map_script_{spaceId}
|
||||
*/
|
||||
createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: getRiskHostCreateMapScriptOptions(spaceId),
|
||||
}),
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 4 Upload script: ml_hostriskscore_reduce_script_{spaceId}
|
||||
*/
|
||||
createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: getRiskHostCreateReduceScriptOptions(spaceId),
|
||||
}),
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 6 create ml_host_risk_score_{spaceId} index
|
||||
*/
|
||||
createIndex({
|
||||
esClient,
|
||||
logger,
|
||||
options: getCreateRiskScoreIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity,
|
||||
}),
|
||||
}),
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 9 create ml_host_risk_score_latest_{spaceId} index
|
||||
*/
|
||||
createIndex({
|
||||
esClient,
|
||||
logger,
|
||||
options: getCreateRiskScoreLatestIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 7 create transform: ml_hostriskscore_pivot_transform_{spaceId}
|
||||
* Step 8 Start the pivot transform
|
||||
*/
|
||||
const createAndStartPivotTransformResult = await createAndStartTransform({
|
||||
esClient,
|
||||
logger,
|
||||
transformId: getRiskScorePivotTransformId(riskScoreEntity, spaceId),
|
||||
options: getCreateMLHostPivotTransformOptions({ spaceId }),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_host_risk_score.console
|
||||
* Step 10 create transform: ml_hostriskscore_latest_transform_{spaceId}
|
||||
* Step 11 Start the latest transform
|
||||
*/
|
||||
const createAndStartLatestTransformResult = await createAndStartTransform({
|
||||
esClient,
|
||||
logger,
|
||||
transformId: getRiskScoreLatestTransformId(riskScoreEntity, spaceId),
|
||||
options: getCreateLatestTransformOptions({ riskScoreEntity, spaceId }),
|
||||
});
|
||||
|
||||
return [
|
||||
...result,
|
||||
createAndStartPivotTransformResult,
|
||||
createAndStartLatestTransformResult,
|
||||
].flat();
|
||||
};
|
||||
|
||||
const createUserRiskScoreIngestPipelineGrouping = async ({
|
||||
esClient,
|
||||
logger,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
}: InstallRiskScoreModule) => {
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 1 Upload script: ml_userriskscore_levels_script_{spaceId}
|
||||
*/
|
||||
const createLevelScriptOptions = getRiskUserCreateLevelScriptOptions(spaceId);
|
||||
|
||||
const createStoredScriptResult = await createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: createLevelScriptOptions,
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 4 Upload ingest pipeline: ml_userriskscore_ingest_pipeline_{spaceId}
|
||||
*/
|
||||
const createIngestPipelineOptions = getRiskScoreIngestPipelineOptions(riskScoreEntity, spaceId);
|
||||
const createIngestPipelineResult = await createIngestPipeline({
|
||||
esClient,
|
||||
logger,
|
||||
options: createIngestPipelineOptions,
|
||||
});
|
||||
|
||||
return [createStoredScriptResult, createIngestPipelineResult];
|
||||
};
|
||||
|
||||
const installUserRiskScoreModule = async ({
|
||||
esClient,
|
||||
logger,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
}: InstallRiskScoreModule) => {
|
||||
const result = await Promise.all([
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 1 Upload script: ml_userriskscore_levels_script_{spaceId}
|
||||
* Step 4 Upload ingest pipeline: ml_userriskscore_ingest_pipeline_{spaceId}
|
||||
*/
|
||||
createUserRiskScoreIngestPipelineGrouping({ esClient, logger, riskScoreEntity, spaceId }),
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 2 Upload script: ml_userriskscore_map_script_{spaceId}
|
||||
*/
|
||||
createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: getRiskUserCreateMapScriptOptions(spaceId),
|
||||
}),
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 3 Upload script: ml_userriskscore_reduce_script_{spaceId}
|
||||
*/
|
||||
createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options: getRiskUserCreateReduceScriptOptions(spaceId),
|
||||
}),
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 5 create ml_user_risk_score_{spaceId} index
|
||||
*/
|
||||
createIndex({
|
||||
esClient,
|
||||
logger,
|
||||
options: getCreateRiskScoreIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity,
|
||||
}),
|
||||
}),
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 8 create ml_user_risk_score_latest_{spaceId} index
|
||||
*/
|
||||
createIndex({
|
||||
esClient,
|
||||
logger,
|
||||
options: getCreateRiskScoreLatestIndicesOptions({
|
||||
spaceId,
|
||||
riskScoreEntity,
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 6 create Transform: ml_userriskscore_pivot_transform_{spaceId}
|
||||
* Step 7 Start the pivot transform
|
||||
*/
|
||||
const createAndStartPivotTransformResult = await createAndStartTransform({
|
||||
esClient,
|
||||
logger,
|
||||
transformId: getRiskScorePivotTransformId(riskScoreEntity, spaceId),
|
||||
options: getCreateMLUserPivotTransformOptions({ spaceId }),
|
||||
});
|
||||
|
||||
/**
|
||||
* console_templates/enable_user_risk_score.console
|
||||
* Step 9 create Transform: ml_userriskscore_latest_transform_{spaceId}
|
||||
* Step 10 Start the latest transform
|
||||
*/
|
||||
const createAndStartLatestTransformResult = await createAndStartTransform({
|
||||
esClient,
|
||||
logger,
|
||||
transformId: getRiskScoreLatestTransformId(riskScoreEntity, spaceId),
|
||||
options: getCreateLatestTransformOptions({ riskScoreEntity, spaceId }),
|
||||
});
|
||||
|
||||
return [
|
||||
...result,
|
||||
createAndStartPivotTransformResult,
|
||||
createAndStartLatestTransformResult,
|
||||
].flat();
|
||||
};
|
||||
|
||||
export const installRiskScoreModule = async (settings: InstallRiskScoreModule) => {
|
||||
if (settings.riskScoreEntity === RiskScoreEntity.user) {
|
||||
const result = await installUserRiskScoreModule(settings);
|
||||
return result;
|
||||
} else {
|
||||
const result = await installHostRiskScoreModule(settings);
|
||||
return result;
|
||||
}
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`installRiskScoreModule - host Create Index: ml_host_risk_score_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create Index: ml_host_risk_score_latest_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"index": "ml_host_risk_score_customSpace",
|
||||
"index": "ml_host_risk_score_latest_default",
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"@timestamp": Object {
|
||||
|
@ -54,9 +54,9 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create Index: ml_host_risk_score_latest_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create Index: ml_host_risk_score_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"index": "ml_host_risk_score_latest_customSpace",
|
||||
"index": "ml_host_risk_score_default",
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"@timestamp": Object {
|
||||
|
@ -108,9 +108,9 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create IngestPipeline: ml_hostriskscore_ingest_pipeline_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create IngestPipeline: ml_hostriskscore_ingest_pipeline_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"name": "ml_hostriskscore_ingest_pipeline_customSpace",
|
||||
"name": "ml_hostriskscore_ingest_pipeline_default",
|
||||
"processors": Array [
|
||||
Object {
|
||||
"set": Object {
|
||||
|
@ -130,7 +130,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"script": Object {
|
||||
"id": "ml_hostriskscore_levels_script_customSpace",
|
||||
"id": "ml_hostriskscore_levels_script_default",
|
||||
"params": Object {
|
||||
"risk_score": "host.risk.calculated_score_norm",
|
||||
},
|
||||
|
@ -140,10 +140,10 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create Transform: ml_hostriskscore_latest_transform_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create and start Transform: ml_hostriskscore_latest_transform_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"dest": Object {
|
||||
"index": "ml_host_risk_score_latest_customSpace",
|
||||
"index": "ml_host_risk_score_latest_default",
|
||||
},
|
||||
"frequency": "1h",
|
||||
"latest": Object {
|
||||
|
@ -154,7 +154,7 @@ Object {
|
|||
},
|
||||
"source": Object {
|
||||
"index": Array [
|
||||
"ml_host_risk_score_customSpace",
|
||||
"ml_host_risk_score_default",
|
||||
],
|
||||
},
|
||||
"sync": Object {
|
||||
|
@ -166,11 +166,11 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create Transform: ml_hostriskscore_pivot_transform_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create and start Transform: ml_hostriskscore_pivot_transform_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"dest": Object {
|
||||
"index": "ml_host_risk_score_customSpace",
|
||||
"pipeline": "ml_hostriskscore_ingest_pipeline_customSpace",
|
||||
"index": "ml_host_risk_score_default",
|
||||
"pipeline": "ml_hostriskscore_ingest_pipeline_default",
|
||||
},
|
||||
"frequency": "1h",
|
||||
"pivot": Object {
|
||||
|
@ -184,10 +184,10 @@ Object {
|
|||
"scripted_metric": Object {
|
||||
"combine_script": "return state",
|
||||
"init_script": Object {
|
||||
"id": "ml_hostriskscore_init_script_customSpace",
|
||||
"id": "ml_hostriskscore_init_script_default",
|
||||
},
|
||||
"map_script": Object {
|
||||
"id": "ml_hostriskscore_map_script_customSpace",
|
||||
"id": "ml_hostriskscore_map_script_default",
|
||||
},
|
||||
"params": Object {
|
||||
"lookback_time": 72,
|
||||
|
@ -215,7 +215,7 @@ Object {
|
|||
"zeta_constant": 2.612,
|
||||
},
|
||||
"reduce_script": Object {
|
||||
"id": "ml_hostriskscore_reduce_script_customSpace",
|
||||
"id": "ml_hostriskscore_reduce_script_default",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -230,7 +230,7 @@ Object {
|
|||
},
|
||||
"source": Object {
|
||||
"index": Array [
|
||||
".alerts-security.alerts-customSpace",
|
||||
".alerts-security.alerts-default",
|
||||
],
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
|
@ -255,9 +255,9 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create script: ml_hostriskscore_init_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create script: ml_hostriskscore_init_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_hostriskscore_init_script_customSpace",
|
||||
"id": "ml_hostriskscore_init_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "state.rule_risk_stats = new HashMap();
|
||||
|
@ -268,9 +268,9 @@ state.tactic_ids = new HashSet();",
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create script: ml_hostriskscore_levels_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create script: ml_hostriskscore_levels_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_hostriskscore_levels_script_customSpace",
|
||||
"id": "ml_hostriskscore_levels_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "double risk_score = (def)ctx.getByPath(params.risk_score);
|
||||
|
@ -293,9 +293,9 @@ else if (risk_score >= 90) {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create script: ml_hostriskscore_map_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create script: ml_hostriskscore_map_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_hostriskscore_map_script_customSpace",
|
||||
"id": "ml_hostriskscore_map_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "// Get the host variant
|
||||
|
@ -324,9 +324,9 @@ state.rule_risk_stats.put(rule_name, stats);",
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Create script: ml_hostriskscore_reduce_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - host Create script: ml_hostriskscore_reduce_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_hostriskscore_reduce_script_customSpace",
|
||||
"id": "ml_hostriskscore_reduce_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "// Consolidating time decayed risks and tactics from across all shards
|
||||
|
@ -407,16 +407,9 @@ return [\\"calculated_score_norm\\": final_risk, \\"rule_risks\\": rule_stats, \
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - host Start Transforms 1`] = `
|
||||
Array [
|
||||
"ml_hostriskscore_pivot_transform_customSpace",
|
||||
"ml_hostriskscore_latest_transform_customSpace",
|
||||
]
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create Index: ml_user_risk_score_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create Index: ml_user_risk_score_latest_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"index": "ml_user_risk_score_customSpace",
|
||||
"index": "ml_user_risk_score_latest_default",
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"@timestamp": Object {
|
||||
|
@ -468,9 +461,9 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create Index: ml_user_risk_score_latest_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create Index: ml_user_risk_score_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"index": "ml_user_risk_score_latest_customSpace",
|
||||
"index": "ml_user_risk_score_default",
|
||||
"mappings": Object {
|
||||
"properties": Object {
|
||||
"@timestamp": Object {
|
||||
|
@ -522,9 +515,9 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create IngestPipeline: ml_userriskscore_ingest_pipeline_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create IngestPipeline: ml_userriskscore_ingest_pipeline_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"name": "ml_userriskscore_ingest_pipeline_customSpace",
|
||||
"name": "ml_userriskscore_ingest_pipeline_default",
|
||||
"processors": Array [
|
||||
Object {
|
||||
"set": Object {
|
||||
|
@ -544,7 +537,7 @@ Object {
|
|||
},
|
||||
Object {
|
||||
"script": Object {
|
||||
"id": "ml_userriskscore_levels_script_customSpace",
|
||||
"id": "ml_userriskscore_levels_script_default",
|
||||
"params": Object {
|
||||
"risk_score": "user.risk.calculated_score_norm",
|
||||
},
|
||||
|
@ -554,10 +547,10 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create Transform: ml_userriskscore_latest_transform_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create Transform: ml_userriskscore_latest_transform_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"dest": Object {
|
||||
"index": "ml_user_risk_score_latest_customSpace",
|
||||
"index": "ml_user_risk_score_latest_default",
|
||||
},
|
||||
"frequency": "1h",
|
||||
"latest": Object {
|
||||
|
@ -568,7 +561,7 @@ Object {
|
|||
},
|
||||
"source": Object {
|
||||
"index": Array [
|
||||
"ml_user_risk_score_customSpace",
|
||||
"ml_user_risk_score_default",
|
||||
],
|
||||
},
|
||||
"sync": Object {
|
||||
|
@ -580,11 +573,11 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create Transform: ml_userriskscore_pivot_transform_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create Transform: ml_userriskscore_pivot_transform_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"dest": Object {
|
||||
"index": "ml_user_risk_score_customSpace",
|
||||
"pipeline": "ml_userriskscore_ingest_pipeline_customSpace",
|
||||
"index": "ml_user_risk_score_default",
|
||||
"pipeline": "ml_userriskscore_ingest_pipeline_default",
|
||||
},
|
||||
"frequency": "1h",
|
||||
"pivot": Object {
|
||||
|
@ -599,7 +592,7 @@ Object {
|
|||
"combine_script": "return state",
|
||||
"init_script": "state.rule_risk_stats = new HashMap();",
|
||||
"map_script": Object {
|
||||
"id": "ml_userriskscore_map_script_customSpace",
|
||||
"id": "ml_userriskscore_map_script_default",
|
||||
},
|
||||
"params": Object {
|
||||
"max_risk": 100,
|
||||
|
@ -607,7 +600,7 @@ Object {
|
|||
"zeta_constant": 2.612,
|
||||
},
|
||||
"reduce_script": Object {
|
||||
"id": "ml_userriskscore_reduce_script_customSpace",
|
||||
"id": "ml_userriskscore_reduce_script_default",
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -622,7 +615,7 @@ Object {
|
|||
},
|
||||
"source": Object {
|
||||
"index": Array [
|
||||
".alerts-security.alerts-customSpace",
|
||||
".alerts-security.alerts-default",
|
||||
],
|
||||
"query": Object {
|
||||
"bool": Object {
|
||||
|
@ -652,9 +645,9 @@ Object {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create script: ml_userriskscore_levels_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create script: ml_userriskscore_levels_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_userriskscore_levels_script_customSpace",
|
||||
"id": "ml_userriskscore_levels_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "double risk_score = (def)ctx.getByPath(params.risk_score);
|
||||
|
@ -677,9 +670,9 @@ else if (risk_score >= 90) {
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create script: ml_userriskscore_map_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create script: ml_userriskscore_map_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_userriskscore_map_script_customSpace",
|
||||
"id": "ml_userriskscore_map_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "// Get running sum of risk score per rule name per shard\\\\\\\\
|
||||
|
@ -691,9 +684,9 @@ state.rule_risk_stats.put(rule_name, stats);",
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Create script: ml_userriskscore_reduce_script_customSpace 1`] = `
|
||||
exports[`installRiskScoresRoute - user Create script: ml_userriskscore_reduce_script_mockSpaceId 1`] = `
|
||||
Object {
|
||||
"id": "ml_userriskscore_reduce_script_customSpace",
|
||||
"id": "ml_userriskscore_reduce_script_default",
|
||||
"script": Object {
|
||||
"lang": "painless",
|
||||
"source": "// Consolidating time decayed risks from across all shards
|
||||
|
@ -741,10 +734,3 @@ return [\\"calculated_score_norm\\": total_norm_risk, \\"rule_risks\\": rule_sta
|
|||
},
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`installRiskScoreModule - user Start Transforms 1`] = `
|
||||
Array [
|
||||
"ml_userriskscore_pivot_transform_customSpace",
|
||||
"ml_userriskscore_latest_transform_customSpace",
|
||||
]
|
||||
`;
|
|
@ -0,0 +1,178 @@
|
|||
/*
|
||||
* 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 { Logger } from '@kbn/core/server';
|
||||
import {
|
||||
serverMock,
|
||||
requestContextMock,
|
||||
requestMock,
|
||||
} from '../../../detection_engine/routes/__mocks__';
|
||||
import { INTERNAL_RISK_SCORE_URL } from '../../../../../common/constants';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import { installRiskScoresRoute } from './install_risk_scores';
|
||||
import { createIngestPipeline } from '../helpers/ingest_pipeline';
|
||||
import { createStoredScript } from '../../stored_scripts/lib/create_script';
|
||||
import { createIndex } from '../../indices/lib/create_index';
|
||||
import { createAndStartTransform } from '../../transform/helpers/transforms';
|
||||
|
||||
jest.mock('../../stored_scripts/lib/create_script', () => ({
|
||||
createStoredScript: jest
|
||||
.fn()
|
||||
.mockImplementation(({ options }) =>
|
||||
Promise.resolve({ [options.id]: { success: true, error: null } })
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../helpers/ingest_pipeline', () => ({
|
||||
createIngestPipeline: jest
|
||||
.fn()
|
||||
.mockImplementation(({ options }) =>
|
||||
Promise.resolve({ [options.name]: { success: true, error: null } })
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../../indices/lib/create_index', () => ({
|
||||
createIndex: jest
|
||||
.fn()
|
||||
.mockImplementation(({ options }) =>
|
||||
Promise.resolve({ [options.index]: { success: true, error: null } })
|
||||
),
|
||||
}));
|
||||
|
||||
jest.mock('../../transform/helpers/transforms', () => ({
|
||||
createAndStartTransform: jest
|
||||
.fn()
|
||||
.mockImplementation(({ transformId }) =>
|
||||
Promise.resolve({ [transformId]: { success: true, error: null } })
|
||||
),
|
||||
}));
|
||||
|
||||
describe(`installRiskScoresRoute - ${RiskScoreEntity.host}`, () => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { context } = requestContextMock.createTools();
|
||||
const logger = { error: jest.fn() } as unknown as Logger;
|
||||
const security = undefined;
|
||||
const mockSpaceId = 'mockSpaceId';
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
server = serverMock.create();
|
||||
({ context } = requestContextMock.createTools());
|
||||
const request = requestMock.create({
|
||||
method: 'post',
|
||||
path: INTERNAL_RISK_SCORE_URL,
|
||||
body: {
|
||||
riskScoreEntity: RiskScoreEntity.host,
|
||||
},
|
||||
});
|
||||
|
||||
installRiskScoresRoute(server.router, logger, security);
|
||||
await server.inject(request, requestContextMock.convertContext(context));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_levels_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create IngestPipeline: ml_${RiskScoreEntity.host}riskscore_ingest_pipeline_${mockSpaceId}`, async () => {
|
||||
expect((createIngestPipeline as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_init_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_map_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[2][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.host}riskscore_reduce_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[3][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.host}_risk_score_${mockSpaceId}`, async () => {
|
||||
expect((createIndex as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.host}_risk_score_latest_${mockSpaceId}`, async () => {
|
||||
expect((createIndex as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create and start Transform: ml_${RiskScoreEntity.host}riskscore_pivot_transform_${mockSpaceId}`, async () => {
|
||||
expect((createAndStartTransform as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create and start Transform: ml_${RiskScoreEntity.host}riskscore_latest_transform_${mockSpaceId}`, async () => {
|
||||
expect((createAndStartTransform as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
|
||||
describe(`installRiskScoresRoute - ${RiskScoreEntity.user}`, () => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { context } = requestContextMock.createTools();
|
||||
const logger = { error: jest.fn() } as unknown as Logger;
|
||||
const security = undefined;
|
||||
const mockSpaceId = 'mockSpaceId';
|
||||
|
||||
beforeAll(async () => {
|
||||
jest.clearAllMocks();
|
||||
|
||||
server = serverMock.create();
|
||||
({ context } = requestContextMock.createTools());
|
||||
const request = requestMock.create({
|
||||
method: 'post',
|
||||
path: INTERNAL_RISK_SCORE_URL,
|
||||
body: {
|
||||
riskScoreEntity: RiskScoreEntity.user,
|
||||
},
|
||||
});
|
||||
|
||||
installRiskScoresRoute(server.router, logger, security);
|
||||
await server.inject(request, requestContextMock.convertContext(context));
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.user}riskscore_levels_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create IngestPipeline: ml_${RiskScoreEntity.user}riskscore_ingest_pipeline_${mockSpaceId}`, async () => {
|
||||
expect((createIngestPipeline as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.user}riskscore_map_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create script: ml_${RiskScoreEntity.user}riskscore_reduce_script_${mockSpaceId}`, async () => {
|
||||
expect((createStoredScript as jest.Mock).mock.calls[2][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.user}_risk_score_${mockSpaceId}`, async () => {
|
||||
expect((createIndex as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Index: ml_${RiskScoreEntity.user}_risk_score_latest_${mockSpaceId}`, async () => {
|
||||
expect((createIndex as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Transform: ml_${RiskScoreEntity.user}riskscore_pivot_transform_${mockSpaceId}`, async () => {
|
||||
expect((createAndStartTransform as jest.Mock).mock.calls[0][0].options).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it(`Create Transform: ml_${RiskScoreEntity.user}riskscore_latest_transform_${mockSpaceId}`, async () => {
|
||||
expect((createAndStartTransform as jest.Mock).mock.calls[1][0].options).toMatchSnapshot();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,64 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
|
||||
import { INTERNAL_RISK_SCORE_URL } from '../../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
|
||||
import type { SetupPlugins } from '../../../../plugin';
|
||||
|
||||
import { buildSiemResponse } from '../../../detection_engine/routes/utils';
|
||||
|
||||
import { installRiskScoreModule } from '../helpers/install_risk_score_module';
|
||||
import { onboardingRiskScoreSchema } from '../schema';
|
||||
|
||||
export const installRiskScoresRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger,
|
||||
security: SetupPlugins['security']
|
||||
) => {
|
||||
router.post(
|
||||
{
|
||||
path: INTERNAL_RISK_SCORE_URL,
|
||||
validate: onboardingRiskScoreSchema,
|
||||
options: {
|
||||
tags: ['access:securitySolution'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const { riskScoreEntity } = request.body;
|
||||
|
||||
try {
|
||||
const securitySolution = await context.securitySolution;
|
||||
|
||||
const spaceId = securitySolution?.getSpaceId();
|
||||
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const esClient = client.asCurrentUser;
|
||||
const res = await installRiskScoreModule({
|
||||
esClient,
|
||||
logger,
|
||||
riskScoreEntity,
|
||||
spaceId,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: res,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
|
||||
export const onboardingRiskScoreSchema = {
|
||||
body: schema.object({
|
||||
riskScoreEntity: schema.oneOf([
|
||||
schema.literal(RiskScoreEntity.host),
|
||||
schema.literal(RiskScoreEntity.user),
|
||||
]),
|
||||
}),
|
||||
};
|
|
@ -5,29 +5,64 @@
|
|||
* 2.0.
|
||||
*/
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
|
||||
import uuid from 'uuid';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import * as savedObjectsToCreate from '../saved_object';
|
||||
import type { SavedObjectTemplate } from '../types';
|
||||
import type { BulkCreateSavedObjectsResult, SavedObjectTemplate } from '../types';
|
||||
import { findOrCreateRiskScoreTag } from './find_or_create_tag';
|
||||
|
||||
export const bulkCreateSavedObjects = async ({
|
||||
export const bulkCreateSavedObjects = async <T = SavedObjectTemplate>({
|
||||
logger,
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
savedObjectTemplate,
|
||||
}: {
|
||||
logger: Logger;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
spaceId?: string;
|
||||
savedObjectTemplate: SavedObjectTemplate;
|
||||
}) => {
|
||||
}): Promise<BulkCreateSavedObjectsResult> => {
|
||||
const regex = /<REPLACE-WITH-SPACE>/g;
|
||||
|
||||
const riskScoreEntity =
|
||||
savedObjectTemplate === 'userRiskScoreDashboards' ? RiskScoreEntity.user : RiskScoreEntity.host;
|
||||
const tag = await findOrCreateRiskScoreTag({ riskScoreEntity, savedObjectsClient, spaceId });
|
||||
|
||||
const tagResponse = await findOrCreateRiskScoreTag({
|
||||
riskScoreEntity,
|
||||
logger,
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
});
|
||||
|
||||
const tagResult = tagResponse?.hostRiskScoreDashboards ?? tagResponse?.userRiskScoreDashboards;
|
||||
|
||||
if (!tagResult?.success) {
|
||||
return tagResponse;
|
||||
}
|
||||
|
||||
const mySavedObjects = savedObjectsToCreate[savedObjectTemplate];
|
||||
|
||||
if (!mySavedObjects) {
|
||||
logger.error(`${savedObjectTemplate} template not found`);
|
||||
return {
|
||||
[savedObjectTemplate]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate('xpack.securitySolution.riskScore.savedObjects.templateNotFoundTitle', {
|
||||
values: { savedObjectTemplate },
|
||||
defaultMessage: `Failed to import saved objects: {savedObjectTemplate} were not created as template not found`,
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const idReplaceMappings: Record<string, string> = {};
|
||||
mySavedObjects.forEach((so) => {
|
||||
if (so.id.startsWith('<REPLACE-WITH-ID')) {
|
||||
|
@ -42,21 +77,40 @@ export const bulkCreateSavedObjects = async ({
|
|||
return {
|
||||
...so,
|
||||
id: idReplaceMappings[so.id] ?? so.id,
|
||||
references: [...references, { id: tag.id, name: tag.name, type: tag.type }],
|
||||
references: [
|
||||
...references,
|
||||
{ id: tagResult?.body?.id, name: tagResult?.body?.name, type: tagResult?.body?.type },
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
const savedObjects = JSON.stringify(mySavedObjectsWithRef);
|
||||
|
||||
if (savedObjects == null) {
|
||||
return new Error('Template not found.');
|
||||
}
|
||||
|
||||
const replacedSO = spaceId ? savedObjects.replace(regex, spaceId) : savedObjects;
|
||||
|
||||
const createSO = await savedObjectsClient.bulkCreate(JSON.parse(replacedSO), {
|
||||
overwrite: true,
|
||||
});
|
||||
try {
|
||||
const result = await savedObjectsClient.bulkCreate<{
|
||||
title: string;
|
||||
name: string;
|
||||
}>(JSON.parse(replacedSO), {
|
||||
overwrite: true,
|
||||
});
|
||||
|
||||
return createSO;
|
||||
return {
|
||||
[savedObjectTemplate]: {
|
||||
success: true,
|
||||
error: null,
|
||||
body: result.saved_objects.map(({ id, type, attributes: { title, name } }) => ({
|
||||
id,
|
||||
type,
|
||||
title,
|
||||
name,
|
||||
})),
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
const err = transformError(error);
|
||||
logger.error(`Failed to create saved object: ${savedObjectTemplate}: ${err.message}`);
|
||||
return { [savedObjectTemplate]: { success: false, error: err } };
|
||||
}
|
||||
};
|
||||
|
|
|
@ -4,10 +4,14 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { SavedObjectsClientContract } from '@kbn/core-saved-objects-api-server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { RiskScoreEntity } from '../../../../../common/search_strategy';
|
||||
import type { Tag } from './utils';
|
||||
import { RISK_SCORE_TAG_DESCRIPTION, getRiskScoreTagName } from './utils';
|
||||
import type { BulkCreateSavedObjectsResult } from '../types';
|
||||
|
||||
export const findRiskScoreTag = async ({
|
||||
savedObjectsClient,
|
||||
|
@ -39,15 +43,17 @@ export const findRiskScoreTag = async ({
|
|||
|
||||
export const findOrCreateRiskScoreTag = async ({
|
||||
riskScoreEntity,
|
||||
logger,
|
||||
savedObjectsClient,
|
||||
spaceId = 'default',
|
||||
}: {
|
||||
logger: Logger;
|
||||
riskScoreEntity: RiskScoreEntity;
|
||||
savedObjectsClient: SavedObjectsClientContract;
|
||||
spaceId?: string;
|
||||
}) => {
|
||||
}): Promise<BulkCreateSavedObjectsResult> => {
|
||||
const tagName = getRiskScoreTagName(riskScoreEntity, spaceId);
|
||||
|
||||
const savedObjectTemplate = `${riskScoreEntity}RiskScoreDashboards`;
|
||||
const existingRiskScoreTag = await findRiskScoreTag({
|
||||
savedObjectsClient,
|
||||
search: tagName,
|
||||
|
@ -61,15 +67,59 @@ export const findOrCreateRiskScoreTag = async ({
|
|||
};
|
||||
|
||||
if (existingRiskScoreTag?.id != null) {
|
||||
return tag;
|
||||
logger.error(`${savedObjectTemplate} already exists`);
|
||||
return {
|
||||
[savedObjectTemplate]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.riskScore.savedObjects.templateAlreadyExistsTitle',
|
||||
{
|
||||
values: { savedObjectTemplate },
|
||||
defaultMessage: `Failed to import saved objects: {savedObjectTemplate} were not created as already exist`,
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
} else {
|
||||
const { id: tagId } = await savedObjectsClient.create('tag', {
|
||||
name: tagName,
|
||||
description: RISK_SCORE_TAG_DESCRIPTION,
|
||||
color: '#6edb7f',
|
||||
});
|
||||
try {
|
||||
const { id: tagId } = await savedObjectsClient.create('tag', {
|
||||
name: tagName,
|
||||
description: RISK_SCORE_TAG_DESCRIPTION,
|
||||
color: '#6edb7f',
|
||||
});
|
||||
|
||||
return { ...tag, id: tagId };
|
||||
return {
|
||||
[savedObjectTemplate]: {
|
||||
success: true,
|
||||
error: null,
|
||||
body: { ...tag, id: tagId },
|
||||
},
|
||||
};
|
||||
} catch (e) {
|
||||
logger.error(
|
||||
`${savedObjectTemplate} cannot be installed as failed to create the tag: ${tagName}`
|
||||
);
|
||||
return {
|
||||
[savedObjectTemplate]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate(
|
||||
'xpack.securitySolution.riskScore.savedObjects.failedToCreateTagTitle',
|
||||
{
|
||||
values: { savedObjectTemplate, tagName },
|
||||
defaultMessage: `Failed to import saved objects: {savedObjectTemplate} were not created as failed to create the tag: {tagName}`,
|
||||
}
|
||||
)
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -17,7 +17,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -136,7 +136,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -159,7 +159,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -190,7 +190,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -221,7 +221,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -252,7 +252,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -270,7 +270,7 @@ Array [
|
|||
"id": "id-7",
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -323,7 +323,7 @@ Array [
|
|||
"type": "tag",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -346,7 +346,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -459,7 +459,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -502,7 +502,7 @@ Array [
|
|||
"type": "tag",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -531,7 +531,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -649,7 +649,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -673,7 +673,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -798,7 +798,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -822,7 +822,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -853,7 +853,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -884,7 +884,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -915,7 +915,7 @@ Array [
|
|||
"type": "index-pattern",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -936,7 +936,7 @@ Array [
|
|||
},
|
||||
"references": Array [
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -995,7 +995,7 @@ Array [
|
|||
"type": "tag",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
@ -1044,7 +1044,7 @@ Array [
|
|||
"type": "tag",
|
||||
},
|
||||
Object {
|
||||
"id": "tagID",
|
||||
"id": "mockTagId",
|
||||
"name": "my tag",
|
||||
"type": "tag",
|
||||
},
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import type { SecurityPluginSetup } from '@kbn/security-plugin/server';
|
||||
import { PREBUILT_SAVED_OBJECTS_BULK_CREATE } from '../../../../../common/constants';
|
||||
import {
|
||||
|
@ -13,18 +14,14 @@ import {
|
|||
requestMock,
|
||||
} from '../../../detection_engine/routes/__mocks__';
|
||||
import { getEmptySavedObjectsResponse } from '../../../detection_engine/routes/__mocks__/request_responses';
|
||||
import { findOrCreateRiskScoreTag } from '../helpers/find_or_create_tag';
|
||||
import { createPrebuiltSavedObjectsRoute } from './create_prebuilt_saved_objects';
|
||||
|
||||
jest.mock('../helpers/find_or_create_tag', () => {
|
||||
const actual = jest.requireActual('../helpers/find_or_create_tag');
|
||||
return {
|
||||
...actual,
|
||||
findOrCreateRiskScoreTag: jest.fn().mockResolvedValue({
|
||||
id: 'tagID',
|
||||
name: 'my tag',
|
||||
description: 'description',
|
||||
type: 'tag',
|
||||
}),
|
||||
findOrCreateRiskScoreTag: jest.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
|
@ -57,6 +54,7 @@ describe('createPrebuiltSavedObjects', () => {
|
|||
let server: ReturnType<typeof serverMock.create>;
|
||||
let securitySetup: SecurityPluginSetup;
|
||||
let { clients, context } = requestContextMock.createTools();
|
||||
const logger = { error: jest.fn() } as unknown as Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
|
@ -73,14 +71,26 @@ describe('createPrebuiltSavedObjects', () => {
|
|||
|
||||
clients.savedObjectsClient.bulkCreate.mockResolvedValue(getEmptySavedObjectsResponse());
|
||||
|
||||
createPrebuiltSavedObjectsRoute(server.router, securitySetup);
|
||||
createPrebuiltSavedObjectsRoute(server.router, logger, securitySetup);
|
||||
});
|
||||
|
||||
it.each([['hostRiskScoreDashboards'], ['userRiskScoreDashboards']])(
|
||||
'should create saved objects from given template - %p',
|
||||
async (object) => {
|
||||
async (templateName) => {
|
||||
(findOrCreateRiskScoreTag as jest.Mock).mockResolvedValue({
|
||||
[templateName]: {
|
||||
success: true,
|
||||
error: null,
|
||||
body: {
|
||||
id: 'mockTagId',
|
||||
name: 'my tag',
|
||||
description: 'description',
|
||||
type: 'tag',
|
||||
},
|
||||
},
|
||||
});
|
||||
const response = await server.inject(
|
||||
createPrebuiltSavedObjectsRequest(object),
|
||||
createPrebuiltSavedObjectsRequest(templateName),
|
||||
requestContextMock.convertContext(context)
|
||||
);
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
|
||||
import { PREBUILT_SAVED_OBJECTS_BULK_CREATE } from '../../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../../types';
|
||||
|
@ -20,6 +20,7 @@ import { createPrebuiltSavedObjectsSchema } from '../schema';
|
|||
|
||||
export const createPrebuiltSavedObjectsRoute = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
logger: Logger,
|
||||
security: SetupPlugins['security']
|
||||
) => {
|
||||
router.post(
|
||||
|
@ -34,29 +35,24 @@ export const createPrebuiltSavedObjectsRoute = (
|
|||
const siemResponse = buildSiemResponse(response);
|
||||
const { template_name: templateName } = request.params;
|
||||
|
||||
try {
|
||||
const securitySolution = await context.securitySolution;
|
||||
const securitySolution = await context.securitySolution;
|
||||
|
||||
const spaceId = securitySolution?.getSpaceId();
|
||||
const spaceId = securitySolution?.getSpaceId();
|
||||
|
||||
const frameworkRequest = await buildFrameworkRequest(context, security, request);
|
||||
const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client;
|
||||
|
||||
const res = await bulkCreateSavedObjects({
|
||||
savedObjectsClient,
|
||||
spaceId,
|
||||
savedObjectTemplate: templateName,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: res,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
const frameworkRequest = await buildFrameworkRequest(context, security, request);
|
||||
const savedObjectsClient = (await frameworkRequest.context.core).savedObjects.client;
|
||||
const result = await bulkCreateSavedObjects({
|
||||
savedObjectsClient,
|
||||
logger,
|
||||
spaceId,
|
||||
savedObjectTemplate: templateName,
|
||||
});
|
||||
const error =
|
||||
result?.hostRiskScoreDashboards?.error || result?.userRiskScoreDashboards?.error;
|
||||
if (error != null) {
|
||||
return siemResponse.error({ statusCode: error.statusCode, body: error.message });
|
||||
} else {
|
||||
return response.ok({ body: result });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -5,4 +5,19 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { OutputError } from '@kbn/securitysolution-es-utils';
|
||||
|
||||
export type SavedObjectTemplate = 'hostRiskScoreDashboards' | 'userRiskScoreDashboards';
|
||||
|
||||
export interface BulkCreateSavedObjectsResult {
|
||||
hostRiskScoreDashboards?: {
|
||||
success: boolean;
|
||||
error: OutputError;
|
||||
body?: { id: string; name: string; type: string };
|
||||
};
|
||||
userRiskScoreDashboards?: {
|
||||
success: boolean;
|
||||
error: OutputError;
|
||||
body?: { id: string; name: string; type: string };
|
||||
};
|
||||
}
|
||||
|
|
|
@ -16,3 +16,6 @@ export { createStoredScriptRoute } from '../stored_scripts/create_script_route';
|
|||
export { deleteStoredScriptRoute } from '../stored_scripts/delete_script_route';
|
||||
|
||||
export { getRiskScoreIndexStatusRoute } from '../index_status';
|
||||
|
||||
export { installRiskScoresRoute } from '../onboarding/routes/install_risk_scores';
|
||||
export { restartTransformRoute } from '../transform/restart_transform';
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import {
|
||||
serverMock,
|
||||
requestContextMock,
|
||||
|
@ -19,27 +19,34 @@ import {
|
|||
import { createStoredScriptRoute } from './create_script_route';
|
||||
import { RISK_SCORE_CREATE_STORED_SCRIPT } from '../../../../common/constants';
|
||||
import { createStoredScript } from './lib/create_script';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
|
||||
const testScriptId = 'test-script';
|
||||
const testScriptSource =
|
||||
'if (state.host_variant_set == false) {\n if (doc.containsKey("host.os.full") && doc["host.os.full"].size() != 0) {\n state.host_variant = doc["host.os.full"].value;\n state.host_variant_set = true;\n }\n}\n// Aggregate all the tactics seen on the host\nif (doc.containsKey("signal.rule.threat.tactic.id") && doc["signal.rule.threat.tactic.id"].size() != 0) {\n state.tactic_ids.add(doc["signal.rule.threat.tactic.id"].value);\n}\n// Get running sum of time-decayed risk score per rule name per shard\nString rule_name = doc["signal.rule.name"].value;\ndef stats = state.rule_risk_stats.getOrDefault(rule_name, [0.0,"",false]);\nint time_diff = (int)((System.currentTimeMillis() - doc["@timestamp"].value.toInstant().toEpochMilli()) / (1000.0 * 60.0 * 60.0));\ndouble risk_derate = Math.min(1, Math.exp((params.lookback_time - time_diff) / params.time_decay_constant));\nstats[0] = Math.max(stats[0], doc["signal.rule.risk_score"].value * risk_derate);\nif (stats[2] == false) {\n stats[1] = doc["kibana.alert.rule.uuid"].value;\n stats[2] = true;\n}\nstate.rule_risk_stats.put(rule_name, stats);';
|
||||
|
||||
jest.mock('./lib/create_script', () => {
|
||||
const actualModule = jest.requireActual('./lib/create_script');
|
||||
return {
|
||||
...actualModule,
|
||||
createStoredScript: jest.fn(),
|
||||
createStoredScript: jest
|
||||
.fn()
|
||||
.mockResolvedValue({ [testScriptId]: { success: true, error: null } }),
|
||||
};
|
||||
});
|
||||
|
||||
describe('createStoredScriptRoute', () => {
|
||||
let server: ReturnType<typeof serverMock.create>;
|
||||
let { context } = requestContextMock.createTools();
|
||||
const logger = { error: jest.fn() } as unknown as Logger;
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetModules();
|
||||
jest.resetAllMocks();
|
||||
jest.clearAllMocks();
|
||||
|
||||
server = serverMock.create();
|
||||
({ context } = requestContextMock.createTools());
|
||||
|
||||
createStoredScriptRoute(server.router);
|
||||
createStoredScriptRoute(server.router, logger);
|
||||
});
|
||||
|
||||
it('Create stored script', async () => {
|
||||
|
@ -47,11 +54,10 @@ describe('createStoredScriptRoute', () => {
|
|||
method: 'put',
|
||||
path: RISK_SCORE_CREATE_STORED_SCRIPT,
|
||||
body: {
|
||||
id: 'test-script',
|
||||
id: testScriptId,
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source:
|
||||
'if (state.host_variant_set == false) {\n if (doc.containsKey("host.os.full") && doc["host.os.full"].size() != 0) {\n state.host_variant = doc["host.os.full"].value;\n state.host_variant_set = true;\n }\n}\n// Aggregate all the tactics seen on the host\nif (doc.containsKey("signal.rule.threat.tactic.id") && doc["signal.rule.threat.tactic.id"].size() != 0) {\n state.tactic_ids.add(doc["signal.rule.threat.tactic.id"].value);\n}\n// Get running sum of time-decayed risk score per rule name per shard\nString rule_name = doc["signal.rule.name"].value;\ndef stats = state.rule_risk_stats.getOrDefault(rule_name, [0.0,"",false]);\nint time_diff = (int)((System.currentTimeMillis() - doc["@timestamp"].value.toInstant().toEpochMilli()) / (1000.0 * 60.0 * 60.0));\ndouble risk_derate = Math.min(1, Math.exp((params.lookback_time - time_diff) / params.time_decay_constant));\nstats[0] = Math.max(stats[0], doc["signal.rule.risk_score"].value * risk_derate);\nif (stats[2] == false) {\n stats[1] = doc["kibana.alert.rule.uuid"].value;\n stats[2] = true;\n}\nstate.rule_risk_stats.put(rule_name, stats);',
|
||||
source: testScriptSource,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
@ -70,4 +76,26 @@ describe('createStoredScriptRoute', () => {
|
|||
|
||||
expect(result.ok).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('return error if failed to create stored script', async () => {
|
||||
(createStoredScript as jest.Mock).mockResolvedValue({
|
||||
[testScriptId]: { success: false, error: transformError(new Error('unknown error')) },
|
||||
});
|
||||
const request = requestMock.create({
|
||||
method: 'put',
|
||||
path: RISK_SCORE_CREATE_STORED_SCRIPT,
|
||||
body: {
|
||||
id: testScriptId,
|
||||
script: {
|
||||
lang: 'painless',
|
||||
source: testScriptSource,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const response = await server.inject(request, requestContextMock.convertContext(context));
|
||||
expect(createStoredScript).toHaveBeenCalled();
|
||||
expect(response.status).toEqual(500);
|
||||
expect(response.body).toEqual({ message: 'unknown error', status_code: 500 });
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,13 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { buildSiemResponse } from '@kbn/lists-plugin/server/routes/utils';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import { RISK_SCORE_CREATE_STORED_SCRIPT } from '../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../types';
|
||||
import { createStoredScriptBodySchema, createStoredScript } from './lib/create_script';
|
||||
|
||||
export const createStoredScriptRoute = (router: SecuritySolutionPluginRouter) => {
|
||||
export const createStoredScriptRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
|
||||
router.put(
|
||||
{
|
||||
path: RISK_SCORE_CREATE_STORED_SCRIPT,
|
||||
|
@ -23,19 +24,25 @@ export const createStoredScriptRoute = (router: SecuritySolutionPluginRouter) =>
|
|||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const esClient = client.asCurrentUser;
|
||||
const options = request.body;
|
||||
|
||||
try {
|
||||
await createStoredScript({
|
||||
client,
|
||||
const result = await createStoredScript({
|
||||
esClient,
|
||||
logger,
|
||||
options,
|
||||
});
|
||||
return response.ok({ body: options });
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
|
||||
const error = result[options.id].error;
|
||||
if (error != null) {
|
||||
return siemResponse.error({ statusCode: error.statusCode, body: error.message });
|
||||
} else {
|
||||
return response.ok({ body: options });
|
||||
}
|
||||
} catch (e) {
|
||||
const error = transformError(e);
|
||||
return siemResponse.error({ statusCode: error.statusCode, body: error.message });
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -6,7 +6,8 @@
|
|||
*/
|
||||
import type { TypeOf } from '@kbn/config-schema';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import type { IScopedClusterClient } from '@kbn/core-elasticsearch-server';
|
||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
|
||||
export const createStoredScriptBodySchema = schema.object({
|
||||
id: schema.string({ minLength: 1 }),
|
||||
|
@ -26,11 +27,20 @@ export const createStoredScriptBodySchema = schema.object({
|
|||
type CreateStoredScriptBodySchema = TypeOf<typeof createStoredScriptBodySchema>;
|
||||
|
||||
export const createStoredScript = async ({
|
||||
client,
|
||||
esClient,
|
||||
logger,
|
||||
options,
|
||||
}: {
|
||||
client: IScopedClusterClient;
|
||||
esClient: ElasticsearchClient;
|
||||
logger: Logger;
|
||||
options: CreateStoredScriptBodySchema;
|
||||
}) => {
|
||||
await client.asCurrentUser.putScript(options);
|
||||
try {
|
||||
await esClient.putScript(options);
|
||||
return { [options.id]: { success: true, error: null } };
|
||||
} catch (error) {
|
||||
const createScriptError = transformError(error);
|
||||
logger.error(`Failed to create stored script: ${options.id}: ${createScriptError.message}`);
|
||||
return { [options.id]: { success: false, error: createScriptError } };
|
||||
}
|
||||
};
|
||||
|
|
|
@ -0,0 +1,241 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { TransformPutTransformRequest } from '@elastic/elasticsearch/lib/api/types';
|
||||
import type { ElasticsearchClient, Logger } from '@kbn/core/server';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
export const TRANSFORM_STATE = {
|
||||
ABORTING: 'aborting',
|
||||
FAILED: 'failed',
|
||||
INDEXING: 'indexing',
|
||||
STARTED: 'started',
|
||||
STOPPED: 'stopped',
|
||||
STOPPING: 'stopping',
|
||||
WAITING: 'waiting',
|
||||
} as const;
|
||||
|
||||
export const createAndStartTransform = ({
|
||||
esClient,
|
||||
transformId,
|
||||
options,
|
||||
logger,
|
||||
}: {
|
||||
esClient: ElasticsearchClient;
|
||||
transformId: string;
|
||||
options: string | Omit<TransformPutTransformRequest, 'transform_id'>;
|
||||
logger: Logger;
|
||||
}) => {
|
||||
const transformOptions = typeof options === 'string' ? JSON.parse(options) : options;
|
||||
const transform = {
|
||||
transform_id: transformId,
|
||||
...transformOptions,
|
||||
};
|
||||
return createTransformIfNotExists(esClient, transform, logger).then((result) => {
|
||||
if (result[transform.transform_id].success) {
|
||||
return startTransformIfNotStarted(esClient, transform.transform_id, logger);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
};
|
||||
/**
|
||||
* Checks if a transform exists, And if not creates it
|
||||
* @param transform - the transform to create. If a transform with the same transform_id already exists, nothing is created.
|
||||
*/
|
||||
export const createTransformIfNotExists = async (
|
||||
esClient: ElasticsearchClient,
|
||||
transform: TransformPutTransformRequest,
|
||||
logger: Logger
|
||||
) => {
|
||||
try {
|
||||
await esClient.transform.getTransform({
|
||||
transform_id: transform.transform_id,
|
||||
});
|
||||
|
||||
logger.error(`Transform ${transform.transform_id} already exists`);
|
||||
return {
|
||||
[transform.transform_id]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate('xpack.securitySolution.riskScore.transform.transformExistsTitle', {
|
||||
values: { transformId: transform.transform_id },
|
||||
defaultMessage: `Failed to create Transform as {transformId} already exists`,
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
} catch (existErr) {
|
||||
const existError = transformError(existErr);
|
||||
if (existError.statusCode === 404) {
|
||||
try {
|
||||
await esClient.transform.putTransform(transform);
|
||||
|
||||
return { [transform.transform_id]: { success: 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 } };
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
`Failed to check if transform ${transform.transform_id} exists before creation: ${existError.message}`
|
||||
);
|
||||
return { [transform.transform_id]: { success: false, error: existError } };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const checkTransformState = async (
|
||||
esClient: ElasticsearchClient,
|
||||
transformId: string,
|
||||
logger: Logger
|
||||
) => {
|
||||
try {
|
||||
const transformStats = await esClient.transform.getTransformStats({
|
||||
transform_id: transformId,
|
||||
});
|
||||
if (transformStats.count <= 0) {
|
||||
logger.error(`Failed to check ${transformId} state: couldn't find transform`);
|
||||
|
||||
return {
|
||||
[transformId]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate('xpack.securitySolution.riskScore.transform.notFoundTitle', {
|
||||
values: { transformId },
|
||||
defaultMessage: `Failed to check Transform state as {transformId} not found`,
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return transformStats.transforms[0];
|
||||
} catch (statsErr) {
|
||||
const statsError = transformError(statsErr);
|
||||
|
||||
logger.error(`Failed to check if transform ${transformId} is started: ${statsError.message}`);
|
||||
return {
|
||||
[transformId]: {
|
||||
success: false,
|
||||
error: statsErr,
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const startTransformIfNotStarted = async (
|
||||
esClient: ElasticsearchClient,
|
||||
transformId: string,
|
||||
logger: Logger
|
||||
) => {
|
||||
const fetchedTransformStats = await checkTransformState(esClient, transformId, logger);
|
||||
if (fetchedTransformStats.state === 'stopped') {
|
||||
try {
|
||||
await esClient.transform.startTransform({ transform_id: transformId });
|
||||
return { [transformId]: { success: true, error: null } };
|
||||
} catch (startErr) {
|
||||
const startError = transformError(startErr);
|
||||
|
||||
logger.error(`Failed starting transform ${transformId}: ${startError.message}`);
|
||||
return {
|
||||
[transformId]: {
|
||||
success: false,
|
||||
error: startError,
|
||||
},
|
||||
};
|
||||
}
|
||||
} else if (
|
||||
fetchedTransformStats.state === TRANSFORM_STATE.STOPPING ||
|
||||
fetchedTransformStats.state === TRANSFORM_STATE.ABORTING ||
|
||||
fetchedTransformStats.state === TRANSFORM_STATE.FAILED
|
||||
) {
|
||||
logger.error(
|
||||
`Not starting transform ${transformId} since it's state is: ${fetchedTransformStats.state}`
|
||||
);
|
||||
return {
|
||||
[transformId]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate('xpack.securitySolution.riskScore.transform.start.stateConflictTitle', {
|
||||
values: { transformId, state: fetchedTransformStats.state },
|
||||
defaultMessage: `Not starting transform {transformId} since it's state is: {state}`,
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const stopTransform = async (
|
||||
esClient: ElasticsearchClient,
|
||||
transformId: string,
|
||||
logger: Logger
|
||||
) => {
|
||||
const fetchedTransformStats = await checkTransformState(esClient, transformId, logger);
|
||||
if (fetchedTransformStats.state) {
|
||||
try {
|
||||
await esClient.transform.stopTransform({
|
||||
transform_id: transformId,
|
||||
force: fetchedTransformStats.state === TRANSFORM_STATE.FAILED,
|
||||
wait_for_completion: true,
|
||||
});
|
||||
return { [transformId]: { success: true, error: null } };
|
||||
} catch (startErr) {
|
||||
const startError = transformError(startErr);
|
||||
|
||||
logger.error(`Failed stopping transform ${transformId}: ${startError.message}`);
|
||||
return {
|
||||
[transformId]: {
|
||||
success: false,
|
||||
error: startError,
|
||||
},
|
||||
};
|
||||
}
|
||||
} else {
|
||||
logger.error(
|
||||
`Not stopping transform ${transformId} since it's state is: ${fetchedTransformStats.state}`
|
||||
);
|
||||
return {
|
||||
[transformId]: {
|
||||
success: false,
|
||||
error: transformError(
|
||||
new Error(
|
||||
i18n.translate('xpack.securitySolution.riskScore.transform.stop.stateConflictTitle', {
|
||||
values: { transformId, state: fetchedTransformStats.state },
|
||||
defaultMessage: `Not stopping transform {transformId} since it's state is: {state}`,
|
||||
})
|
||||
)
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export const restartTransform = (
|
||||
esClient: ElasticsearchClient,
|
||||
transformId: string,
|
||||
logger: Logger
|
||||
) => {
|
||||
return stopTransform(esClient, transformId, logger).then((result) => {
|
||||
if (result[transformId].success) {
|
||||
return startTransformIfNotStarted(esClient, transformId, logger);
|
||||
} else {
|
||||
return result;
|
||||
}
|
||||
});
|
||||
};
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { Logger } from '@kbn/core/server';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { RISK_SCORE_RESTART_TRANSFORMS } from '../../../../common/constants';
|
||||
import type { SecuritySolutionPluginRouter } from '../../../types';
|
||||
|
||||
import { buildSiemResponse } from '../../detection_engine/routes/utils';
|
||||
|
||||
import { RiskScoreEntity } from '../../../../common/search_strategy';
|
||||
import { restartTransform } from './helpers/transforms';
|
||||
import {
|
||||
getRiskScoreLatestTransformId,
|
||||
getRiskScorePivotTransformId,
|
||||
} from '../../../../common/utils/risk_score_modules';
|
||||
|
||||
const restartRiskScoreTransformsSchema = {
|
||||
body: schema.object({
|
||||
riskScoreEntity: schema.oneOf([
|
||||
schema.literal(RiskScoreEntity.host),
|
||||
schema.literal(RiskScoreEntity.user),
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
export const restartTransformRoute = (router: SecuritySolutionPluginRouter, logger: Logger) => {
|
||||
router.post(
|
||||
{
|
||||
path: RISK_SCORE_RESTART_TRANSFORMS,
|
||||
validate: restartRiskScoreTransformsSchema,
|
||||
options: {
|
||||
tags: ['access:securitySolution'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const siemResponse = buildSiemResponse(response);
|
||||
const { riskScoreEntity } = request.body;
|
||||
|
||||
try {
|
||||
const securitySolution = await context.securitySolution;
|
||||
|
||||
const spaceId = securitySolution?.getSpaceId();
|
||||
|
||||
const { client } = (await context.core).elasticsearch;
|
||||
const esClient = client.asCurrentUser;
|
||||
const restartPivotTransformResult = await restartTransform(
|
||||
esClient,
|
||||
getRiskScorePivotTransformId(riskScoreEntity, spaceId),
|
||||
logger
|
||||
);
|
||||
|
||||
const restartLatestTransformResult = await restartTransform(
|
||||
esClient,
|
||||
getRiskScoreLatestTransformId(riskScoreEntity, spaceId),
|
||||
logger
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
body: [restartPivotTransformResult, restartLatestTransformResult],
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
return siemResponse.error({
|
||||
body: error.message,
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
|
@ -82,7 +82,9 @@ import {
|
|||
deletePrebuiltSavedObjectsRoute,
|
||||
deleteStoredScriptRoute,
|
||||
getRiskScoreIndexStatusRoute,
|
||||
installRiskScoresRoute,
|
||||
readPrebuiltDevToolContentRoute,
|
||||
restartTransformRoute,
|
||||
} from '../lib/risk_score/routes';
|
||||
export const initRoutes = (
|
||||
router: SecuritySolutionPluginRouter,
|
||||
|
@ -185,15 +187,16 @@ export const initRoutes = (
|
|||
getSourcererDataViewRoute(router, getStartServices);
|
||||
|
||||
// risky score module
|
||||
createEsIndexRoute(router);
|
||||
createEsIndexRoute(router, logger);
|
||||
deleteEsIndicesRoute(router);
|
||||
createStoredScriptRoute(router);
|
||||
createStoredScriptRoute(router, logger);
|
||||
deleteStoredScriptRoute(router);
|
||||
readPrebuiltDevToolContentRoute(router);
|
||||
createPrebuiltSavedObjectsRoute(router, security);
|
||||
createPrebuiltSavedObjectsRoute(router, logger, security);
|
||||
deletePrebuiltSavedObjectsRoute(router, security);
|
||||
getRiskScoreIndexStatusRoute(router);
|
||||
|
||||
installRiskScoresRoute(router, logger, security);
|
||||
restartTransformRoute(router, logger);
|
||||
const { previewTelemetryUrlEnabled } = config.experimentalFeatures;
|
||||
if (previewTelemetryUrlEnabled) {
|
||||
// telemetry preview endpoint for e2e integration tests only at the moment.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue