Skip to content

Commit

Permalink
[SIEM] [Detections] [BUG] Feedback to user about generated encryption…
Browse files Browse the repository at this point in the history
… key (#56464)

* wip

* Expose whether the encryption key is randomly generated for saved-objects

* give feedback to user if encryption key is randomly generated

* remove package distributable

* update msg for no api integration key

* Update x-pack/plugins/encrypted_saved_objects/server/config.ts

Co-Authored-By: Brandon Kobel <brandon.kobel@gmail.com>

* review II

* fix type

* rename encryptionKeyRandomlyGenerated ->  usingEphemeralEncryptionKey

* fix test and mistake

Co-authored-by: Brandon Kobel <brandon.kobel@gmail.com>
  • Loading branch information
XavierM and kobelb authored Jan 31, 2020
1 parent 49e9c79 commit 0a17cde
Show file tree
Hide file tree
Showing 22 changed files with 180 additions and 43 deletions.
4 changes: 3 additions & 1 deletion x-pack/legacy/plugins/encrypted_saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export const encryptedSavedObjects = (kibana: {
// Some legacy plugins still use `enabled` config key, so we keep it here, but the rest of the
// keys is handled by the New Platform plugin.
config: (Joi: Root) =>
Joi.object({ enabled: Joi.boolean().default(true) })
Joi.object({
enabled: Joi.boolean().default(true),
})
.unknown(true)
.default(),

Expand Down
4 changes: 4 additions & 0 deletions x-pack/legacy/plugins/siem/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
*/

import { i18n } from '@kbn/i18n';
import { get } from 'lodash/fp';
import { resolve } from 'path';
import { Server } from 'hapi';
import { Root } from 'joi';
Expand Down Expand Up @@ -155,6 +156,9 @@ export const siem = (kibana: any) => {
const initializerContext = { ...coreContext, env } as PluginInitializerContext;
const serverFacade = {
config,
usingEphemeralEncryptionKey:
get('usingEphemeralEncryptionKey', newPlatform.setup.plugins.encryptedSavedObjects) ??
false,
plugins: {
alerting: plugins.alerting,
actions: newPlatform.start.plugins.actions,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ interface UsePrePackagedRuleProps {
hasIndexWrite: boolean | null;
hasManageApiKey: boolean | null;
isAuthenticated: boolean | null;
hasEncryptionKey: boolean | null;
isSignalIndexExists: boolean | null;
}

Expand All @@ -38,6 +39,7 @@ interface UsePrePackagedRuleProps {
* @param hasIndexWrite boolean
* @param hasManageApiKey boolean
* @param isAuthenticated boolean
* @param hasEncryptionKey boolean
* @param isSignalIndexExists boolean
*
*/
Expand All @@ -46,6 +48,7 @@ export const usePrePackagedRules = ({
hasIndexWrite,
hasManageApiKey,
isAuthenticated,
hasEncryptionKey,
isSignalIndexExists,
}: UsePrePackagedRuleProps): Return => {
const [rulesStatus, setRuleStatus] = useState<
Expand Down Expand Up @@ -117,6 +120,7 @@ export const usePrePackagedRules = ({
hasIndexWrite &&
hasManageApiKey &&
isAuthenticated &&
hasEncryptionKey &&
isSignalIndexExists
) {
setLoadingCreatePrePackagedRules(true);
Expand Down Expand Up @@ -180,7 +184,14 @@ export const usePrePackagedRules = ({
isSubscribed = false;
abortCtrl.abort();
};
}, [canUserCRUD, hasIndexWrite, hasManageApiKey, isAuthenticated, isSignalIndexExists]);
}, [
canUserCRUD,
hasIndexWrite,
hasManageApiKey,
isAuthenticated,
hasEncryptionKey,
isSignalIndexExists,
]);

return {
loading,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,4 +97,5 @@ export interface Privilege {
};
};
is_authenticated: boolean;
has_encryption_key: boolean;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import * as i18n from './translations';
interface Return {
loading: boolean;
isAuthenticated: boolean | null;
hasEncryptionKey: boolean | null;
hasIndexManage: boolean | null;
hasManageApiKey: boolean | null;
hasIndexWrite: boolean | null;
Expand All @@ -25,9 +26,17 @@ interface Return {
export const usePrivilegeUser = (): Return => {
const [loading, setLoading] = useState(true);
const [privilegeUser, setPrivilegeUser] = useState<
Pick<Return, 'isAuthenticated' | 'hasIndexManage' | 'hasManageApiKey' | 'hasIndexWrite'>
Pick<
Return,
| 'isAuthenticated'
| 'hasEncryptionKey'
| 'hasIndexManage'
| 'hasManageApiKey'
| 'hasIndexWrite'
>
>({
isAuthenticated: null,
hasEncryptionKey: null,
hasIndexManage: null,
hasManageApiKey: null,
hasIndexWrite: null,
Expand All @@ -50,6 +59,7 @@ export const usePrivilegeUser = (): Return => {
const indexName = Object.keys(privilege.index)[0];
setPrivilegeUser({
isAuthenticated: privilege.is_authenticated,
hasEncryptionKey: privilege.has_encryption_key,
hasIndexManage: privilege.index[indexName].manage,
hasIndexWrite:
privilege.index[indexName].create ||
Expand All @@ -67,6 +77,7 @@ export const usePrivilegeUser = (): Return => {
if (isSubscribed) {
setPrivilegeUser({
isAuthenticated: false,
hasEncryptionKey: false,
hasIndexManage: false,
hasManageApiKey: false,
hasIndexWrite: false,
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
* you may not use this file except in compliance with the Elastic License.
*/

import { EuiCallOut, EuiButton } from '@elastic/eui';
import React, { memo, useCallback, useState } from 'react';

import * as i18n from './translations';

const NoApiIntegrationKeyCallOutComponent = () => {
const [showCallOut, setShowCallOut] = useState(true);
const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]);

return showCallOut ? (
<EuiCallOut title={i18n.NO_API_INTEGRATION_KEY_CALLOUT_TITLE} color="danger" iconType="alert">
<p>{i18n.NO_API_INTEGRATION_KEY_CALLOUT_MSG}</p>
<EuiButton color="danger" onClick={handleCallOut}>
{i18n.DISMISS_CALLOUT}
</EuiButton>
</EuiCallOut>
) : null;
};

export const NoApiIntegrationKeyCallOut = memo(NoApiIntegrationKeyCallOutComponent);
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* 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 { i18n } from '@kbn/i18n';

export const NO_API_INTEGRATION_KEY_CALLOUT_TITLE = i18n.translate(
'xpack.siem.detectionEngine.noApiIntegrationKeyCallOutTitle',
{
defaultMessage: 'API integration key required',
}
);

export const NO_API_INTEGRATION_KEY_CALLOUT_MSG = i18n.translate(
'xpack.siem.detectionEngine.noApiIntegrationKeyCallOutMsg',
{
defaultMessage: `A new encryption key is generated for saved objects each time you start Kibana. Without a persistent key, you cannot delete or modify rules after Kibana restarts. To set a persistent key, add the xpack.encryptedSavedObjects.encryptionKey setting with any text value of 32 or more characters to the kibana.yml file.`,
}
);

export const DISMISS_CALLOUT = i18n.translate(
'xpack.siem.detectionEngine.dismissNoApiIntegrationKeyButton',
{
defaultMessage: 'Dismiss',
}
);
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export interface State {
hasManageApiKey: boolean | null;
isSignalIndexExists: boolean | null;
isAuthenticated: boolean | null;
hasEncryptionKey: boolean | null;
loading: boolean;
signalIndexName: string | null;
}
Expand All @@ -29,6 +30,7 @@ const initialState: State = {
hasManageApiKey: null,
isSignalIndexExists: null,
isAuthenticated: null,
hasEncryptionKey: null,
loading: true,
signalIndexName: null,
};
Expand All @@ -55,6 +57,10 @@ export type Action =
type: 'updateIsAuthenticated';
isAuthenticated: boolean | null;
}
| {
type: 'updateHasEncryptionKey';
hasEncryptionKey: boolean | null;
}
| {
type: 'updateCanUserCRUD';
canUserCRUD: boolean | null;
Expand Down Expand Up @@ -102,6 +108,12 @@ export const userInfoReducer = (state: State, action: Action): State => {
isAuthenticated: action.isAuthenticated,
};
}
case 'updateHasEncryptionKey': {
return {
...state,
hasEncryptionKey: action.hasEncryptionKey,
};
}
case 'updateCanUserCRUD': {
return {
...state,
Expand Down Expand Up @@ -142,6 +154,7 @@ export const useUserInfo = (): State => {
hasManageApiKey,
isSignalIndexExists,
isAuthenticated,
hasEncryptionKey,
loading,
signalIndexName,
},
Expand All @@ -150,6 +163,7 @@ export const useUserInfo = (): State => {
const {
loading: privilegeLoading,
isAuthenticated: isApiAuthenticated,
hasEncryptionKey: isApiEncryptionKey,
hasIndexManage: hasApiIndexManage,
hasIndexWrite: hasApiIndexWrite,
hasManageApiKey: hasApiManageApiKey,
Expand Down Expand Up @@ -205,6 +219,12 @@ export const useUserInfo = (): State => {
}
}, [loading, isAuthenticated, isApiAuthenticated]);

useEffect(() => {
if (!loading && hasEncryptionKey !== isApiEncryptionKey && isApiEncryptionKey != null) {
dispatch({ type: 'updateHasEncryptionKey', hasEncryptionKey: isApiEncryptionKey });
}
}, [loading, hasEncryptionKey, isApiEncryptionKey]);

useEffect(() => {
if (!loading && canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) {
dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD });
Expand All @@ -220,19 +240,21 @@ export const useUserInfo = (): State => {
useEffect(() => {
if (
isAuthenticated &&
hasEncryptionKey &&
hasIndexManage &&
isSignalIndexExists != null &&
!isSignalIndexExists &&
createSignalIndex != null
) {
createSignalIndex();
}
}, [createSignalIndex, isAuthenticated, isSignalIndexExists, hasIndexManage]);
}, [createSignalIndex, isAuthenticated, hasEncryptionKey, isSignalIndexExists, hasIndexManage]);

return {
loading,
isSignalIndexExists,
isAuthenticated,
hasEncryptionKey,
canUserCRUD,
hasIndexManage,
hasIndexWrite,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ import { GlobalTime } from '../../containers/global_time';
import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source';
import { AlertsTable } from '../../components/alerts_viewer/alerts_table';
import { FiltersGlobal } from '../../components/filters_global';
import { DETECTION_ENGINE_PAGE_NAME } from '../../components/link_to/redirect_to_detection_engine';
import {
getDetectionEngineTabUrl,
getRulesUrl,
} from '../../components/link_to/redirect_to_detection_engine';
import { SiemSearchBar } from '../../components/search_bar';
import { WrapperPage } from '../../components/wrapper_page';
import { State } from '../../store';
Expand All @@ -30,6 +33,7 @@ import { InputsRange } from '../../store/inputs/model';
import { AlertsByCategory } from '../overview/alerts_by_category';
import { useSignalInfo } from './components/signals_info';
import { SignalsTable } from './components/signals';
import { NoApiIntegrationKeyCallOut } from './components/no_api_integration_callout';
import { NoWriteSignalsCallOut } from './components/no_write_signals_callout';
import { SignalsHistogramPanel } from './components/signals_histogram_panel';
import { signalsHistogramOptions } from './components/signals_histogram_panel/config';
Expand Down Expand Up @@ -79,6 +83,7 @@ const DetectionEnginePageComponent: React.FC<DetectionEnginePageComponentProps>
loading,
isSignalIndexExists,
isAuthenticated: isUserAuthenticated,
hasEncryptionKey,
canUserCRUD,
signalIndexName,
hasIndexWrite,
Expand All @@ -101,7 +106,7 @@ const DetectionEnginePageComponent: React.FC<DetectionEnginePageComponentProps>
isSelected={tab.id === tabName}
disabled={tab.disabled}
key={tab.id}
href={`#/${DETECTION_ENGINE_PAGE_NAME}/${tab.id}`}
href={getDetectionEngineTabUrl(tab.id)}
>
{tab.name}
</EuiTab>
Expand Down Expand Up @@ -134,6 +139,7 @@ const DetectionEnginePageComponent: React.FC<DetectionEnginePageComponentProps>

return (
<>
{hasEncryptionKey != null && !hasEncryptionKey && <NoApiIntegrationKeyCallOut />}
{hasIndexWrite != null && !hasIndexWrite && <NoWriteSignalsCallOut />}
<WithSource sourceId="default" indexToAdd={indexToAdd}>
{({ indicesExist, indexPattern }) => {
Expand All @@ -155,7 +161,7 @@ const DetectionEnginePageComponent: React.FC<DetectionEnginePageComponentProps>
}
title={i18n.PAGE_TITLE}
>
<EuiButton fill href="#/detections/rules" iconType="gear">
<EuiButton fill href={getRulesUrl()} iconType="gear">
{i18n.BUTTON_MANAGE_RULES}
</EuiButton>
</DetectionEngineHeaderPage>
Expand Down Expand Up @@ -184,7 +190,7 @@ const DetectionEnginePageComponent: React.FC<DetectionEnginePageComponentProps>
<SignalsTable
loading={loading}
hasIndexWrite={hasIndexWrite ?? false}
canUserCRUD={canUserCRUD ?? false}
canUserCRUD={(canUserCRUD ?? false) && (hasEncryptionKey ?? false)}
from={from}
signalsIndex={signalIndexName ?? ''}
to={to}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { StepDefineRule } from '../components/step_define_rule';
import { StepScheduleRule } from '../components/step_schedule_rule';
import { DetectionEngineHeaderPage } from '../../components/detection_engine_header_page';
import * as RuleI18n from '../translations';
import { redirectToDetections } from '../helpers';
import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types';
import { formatRule } from './helpers';
import * as i18n from './translations';
Expand Down Expand Up @@ -69,6 +70,7 @@ const CreateRulePageComponent: React.FC = () => {
loading,
isSignalIndexExists,
isAuthenticated,
hasEncryptionKey,
canUserCRUD,
hasManageApiKey,
} = useUserInfo();
Expand Down Expand Up @@ -239,11 +241,7 @@ const CreateRulePageComponent: React.FC = () => {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}/rules`} />;
}

if (
isSignalIndexExists != null &&
isAuthenticated != null &&
(!isSignalIndexExists || !isAuthenticated)
) {
if (redirectToDetections(isSignalIndexExists, isAuthenticated, hasEncryptionKey)) {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}`} />;
} else if (userHasNoPermissions) {
return <Redirect to={`/${DETECTION_ENGINE_PAGE_NAME}/rules`} />;
Expand Down
Loading

0 comments on commit 0a17cde

Please sign in to comment.