Skip to content

Commit

Permalink
[SIEM] Detections create prepackage rules (elastic#55403)
Browse files Browse the repository at this point in the history
* update extra action on rule detail to match design

* remove experimental label

* allow pre-package to be deleted + do not allow wrong user to create pre-packages rules

* Additional look back minimum value to 1

* fix flow with edit rule

* add success toaster when rule is created or updated

* Fix Timeline selector loading

* review ben doc + change detectin engine to detection even in url

* Succeeded text size consistency in rule details page

* fix description of threats

* fix test

* fix type

* fix internatinalization

* adding pre-packaged rules

* fix bug + enhance ux

* unified icon

* fix i18n

* fix bugs

* review I

* review II

* add border back
  • Loading branch information
XavierM committed Jan 22, 2020
1 parent e48317f commit 5442cf6
Show file tree
Hide file tree
Showing 22 changed files with 870 additions and 364 deletions.
3 changes: 2 additions & 1 deletion x-pack/legacy/plugins/siem/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export const DETECTION_ENGINE_PREPACKAGED_URL = `${DETECTION_ENGINE_RULES_URL}/p
export const DETECTION_ENGINE_PRIVILEGES_URL = `${DETECTION_ENGINE_URL}/privileges`;
export const DETECTION_ENGINE_INDEX_URL = `${DETECTION_ENGINE_URL}/index`;
export const DETECTION_ENGINE_TAGS_URL = `${DETECTION_ENGINE_URL}/tags`;
export const DETECTION_ENGINE_RULES_STATUS = `${DETECTION_ENGINE_URL}/rules/_find_statuses`;
export const DETECTION_ENGINE_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/_find_statuses`;
export const DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL = `${DETECTION_ENGINE_RULES_URL}/prepackaged/_status`;

/**
* Default signals index key for kibana.dev.yml
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ import { throwIfNotOk } from '../../../hooks/api/api';
import {
DETECTION_ENGINE_RULES_URL,
DETECTION_ENGINE_PREPACKAGED_URL,
DETECTION_ENGINE_RULES_STATUS,
DETECTION_ENGINE_RULES_STATUS_URL,
DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL,
} from '../../../../common/constants';
import * as i18n from '../../../pages/detection_engine/rules/translations';

Expand Down Expand Up @@ -63,7 +64,7 @@ export const addRule = async ({ rule, signal }: AddRulesProps): Promise<NewRule>
export const fetchRules = async ({
filterOptions = {
filter: '',
sortField: 'enabled',
sortField: 'name',
sortOrder: 'desc',
},
pagination = {
Expand Down Expand Up @@ -313,6 +314,7 @@ export const exportRules = async ({
* Get Rule Status provided Rule ID
*
* @param id string of Rule ID's (not rule_id)
* @param signal AbortSignal for cancelling request
*
* @throws An error if response is not OK
*/
Expand All @@ -324,7 +326,7 @@ export const getRuleStatusById = async ({
signal: AbortSignal;
}): Promise<Record<string, RuleStatus>> => {
const response = await fetch(
`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS}?ids=${encodeURIComponent(
`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_STATUS_URL}?ids=${encodeURIComponent(
JSON.stringify([id])
)}`,
{
Expand All @@ -341,3 +343,36 @@ export const getRuleStatusById = async ({
await throwIfNotOk(response);
return response.json();
};

/**
* Get pre packaged rules Status
*
* @param signal AbortSignal for cancelling request
*
* @throws An error if response is not OK
*/
export const getPrePackagedRulesStatus = async ({
signal,
}: {
signal: AbortSignal;
}): Promise<{
rules_installed: number;
rules_not_installed: number;
rules_not_updated: number;
}> => {
const response = await fetch(
`${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_RULES_STATUS_URL}`,
{
method: 'GET',
credentials: 'same-origin',
headers: {
'content-type': 'application/json',
'kbn-xsrf': 'true',
},
signal,
}
);

await throwIfNotOk(response);
return response.json();
};
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ export * from './persist_rule';
export * from './types';
export * from './use_rule';
export * from './use_rules';
export * from './use_pre_packaged_rules';
export * from './use_rule_status';
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,17 @@ export const RULE_ADD_FAILURE = i18n.translate(
defaultMessage: 'Failed to add Rule',
}
);

export const RULE_PREPACKAGED_FAILURE = i18n.translate(
'xpack.siem.containers.detectionEngine.createPrePackagedRuleFailDescription',
{
defaultMessage: 'Failed to installed pre-packaged rules from elastic',
}
);

export const RULE_PREPACKAGED_SUCCESS = i18n.translate(
'xpack.siem.containers.detectionEngine.createPrePackagedRuleSuccesDescription',
{
defaultMessage: 'Installed pre-packaged rules from elastic',
}
);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { useEffect, useState, useRef } from 'react';

import { useStateToaster, displaySuccessToast } from '../../../components/toasters';
import { errorToToaster } from '../../../components/ml/api/error_to_toaster';
import { getPrePackagedRulesStatus, createPrepackagedRules } from './api';
import * as i18n from './translations';

type Func = () => void;
export type CreatePreBuiltRules = () => Promise<boolean>;
interface Return {
createPrePackagedRules: null | CreatePreBuiltRules;
loading: boolean;
loadingCreatePrePackagedRules: boolean;
refetchPrePackagedRulesStatus: Func | null;
rulesInstalled: number | null;
rulesNotInstalled: number | null;
rulesNotUpdated: number | null;
}

interface UsePrePackagedRuleProps {
canUserCRUD: boolean | null;
hasIndexManage: boolean | null;
hasManageApiKey: boolean | null;
isAuthenticated: boolean | null;
isSignalIndexExists: boolean | null;
}

/**
* Hook for using to get status about pre-packaged Rules from the Detection Engine API
*
* @param hasIndexManage boolean
* @param hasManageApiKey boolean
* @param isAuthenticated boolean
* @param isSignalIndexExists boolean
*
*/
export const usePrePackagedRules = ({
canUserCRUD,
hasIndexManage,
hasManageApiKey,
isAuthenticated,
isSignalIndexExists,
}: UsePrePackagedRuleProps): Return => {
const [rulesInstalled, setRulesInstalled] = useState<number | null>(null);
const [rulesNotInstalled, setRulesNotInstalled] = useState<number | null>(null);
const [rulesNotUpdated, setRulesNotUpdated] = useState<number | null>(null);
const [loadingCreatePrePackagedRules, setLoadingCreatePrePackagedRules] = useState(false);
const [loading, setLoading] = useState(true);
const createPrePackagedRules = useRef<null | CreatePreBuiltRules>(null);
const refetchPrePackagedRules = useRef<Func | null>(null);
const [, dispatchToaster] = useStateToaster();

useEffect(() => {
let isSubscribed = true;
const abortCtrl = new AbortController();

const fetchPrePackagedRules = async () => {
try {
setLoading(true);
const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({
signal: abortCtrl.signal,
});

if (isSubscribed) {
setRulesInstalled(prePackagedRuleStatusResponse.rules_installed);
setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed);
setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated);
}
} catch (error) {
if (isSubscribed) {
setRulesInstalled(null);
setRulesNotInstalled(null);
setRulesNotUpdated(null);
errorToToaster({ title: i18n.RULE_FETCH_FAILURE, error, dispatchToaster });
}
}
if (isSubscribed) {
setLoading(false);
}
};

const createElasticRules = async (): Promise<boolean> => {
return new Promise(async resolve => {
try {
if (
canUserCRUD &&
hasIndexManage &&
hasManageApiKey &&
isAuthenticated &&
isSignalIndexExists
) {
setLoadingCreatePrePackagedRules(true);
await createPrepackagedRules({
signal: abortCtrl.signal,
});

if (isSubscribed) {
let iterationTryOfFetchingPrePackagedCount = 0;
let timeoutId = -1;
const stopTimeOut = () => {
if (timeoutId !== -1) {
window.clearTimeout(timeoutId);
}
};
const reFetch = () =>
window.setTimeout(async () => {
iterationTryOfFetchingPrePackagedCount =
iterationTryOfFetchingPrePackagedCount + 1;
const prePackagedRuleStatusResponse = await getPrePackagedRulesStatus({
signal: abortCtrl.signal,
});
if (
isSubscribed &&
((prePackagedRuleStatusResponse.rules_not_installed === 0 &&
prePackagedRuleStatusResponse.rules_not_updated === 0) ||
iterationTryOfFetchingPrePackagedCount > 100)
) {
setLoadingCreatePrePackagedRules(false);
setRulesInstalled(prePackagedRuleStatusResponse.rules_installed);
setRulesNotInstalled(prePackagedRuleStatusResponse.rules_not_installed);
setRulesNotUpdated(prePackagedRuleStatusResponse.rules_not_updated);
displaySuccessToast(i18n.RULE_PREPACKAGED_SUCCESS, dispatchToaster);
stopTimeOut();
resolve(true);
} else {
timeoutId = reFetch();
}
}, 300);
timeoutId = reFetch();
}
}
} catch (error) {
if (isSubscribed) {
setLoadingCreatePrePackagedRules(false);
errorToToaster({ title: i18n.RULE_PREPACKAGED_FAILURE, error, dispatchToaster });
resolve(false);
}
}
});
};

fetchPrePackagedRules();
createPrePackagedRules.current = createElasticRules;
refetchPrePackagedRules.current = fetchPrePackagedRules;
return () => {
isSubscribed = false;
abortCtrl.abort();
};
}, [canUserCRUD, hasIndexManage, hasManageApiKey, isAuthenticated, isSignalIndexExists]);

return {
loading,
loadingCreatePrePackagedRules,
refetchPrePackagedRulesStatus: refetchPrePackagedRules.current,
rulesInstalled,
rulesNotInstalled,
rulesNotUpdated,
createPrePackagedRules: createPrePackagedRules.current,
};
};
Loading

0 comments on commit 5442cf6

Please sign in to comment.