mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 09:19:04 -04:00
[Osquery] [PoC] Add alert attachment along with Osquery Results to Cases (#143674)
This commit is contained in:
parent
9e19d09e37
commit
cf152eae23
14 changed files with 96 additions and 14 deletions
|
@ -5,12 +5,13 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react';
|
||||
import React, { useCallback, useContext, useMemo } from 'react';
|
||||
import { CommentType, ExternalReferenceStorageType } from '@kbn/cases-plugin/common';
|
||||
import { EuiButtonEmpty, EuiButtonIcon, EuiFlexItem, EuiToolTip } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import type { CaseAttachmentsWithoutOwner } from '@kbn/cases-plugin/public';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { AlertAttachmentContext } from '../common/contexts';
|
||||
|
||||
const ADD_TO_CASE = i18n.translate(
|
||||
'xpack.osquery.pack.queriesTable.addToCaseResultsActionAriaLabel',
|
||||
|
@ -37,6 +38,24 @@ export const AddToCaseButton: React.FC<AddToCaseButtonProps> = ({
|
|||
iconProps,
|
||||
}) => {
|
||||
const { cases } = useKibana().services;
|
||||
const ecsData = useContext(AlertAttachmentContext);
|
||||
const alertAttachments = useMemo(
|
||||
() =>
|
||||
ecsData?._id
|
||||
? [
|
||||
{
|
||||
alertId: ecsData?._id ?? '',
|
||||
index: ecsData?._index ?? '',
|
||||
rule: cases.helpers.getRuleIdFromEvent({
|
||||
ecs: ecsData,
|
||||
data: [],
|
||||
}),
|
||||
type: CommentType.alert as const,
|
||||
},
|
||||
]
|
||||
: [],
|
||||
[cases.helpers, ecsData]
|
||||
);
|
||||
|
||||
const casePermissions = cases.helpers.canUseCases();
|
||||
const hasCasesPermissions =
|
||||
|
@ -45,6 +64,7 @@ export const AddToCaseButton: React.FC<AddToCaseButtonProps> = ({
|
|||
|
||||
const handleClick = useCallback(() => {
|
||||
const attachments: CaseAttachmentsWithoutOwner = [
|
||||
...alertAttachments,
|
||||
{
|
||||
type: CommentType.externalReference,
|
||||
externalReferenceId: actionId,
|
||||
|
@ -58,7 +78,7 @@ export const AddToCaseButton: React.FC<AddToCaseButtonProps> = ({
|
|||
if (hasCasesPermissions) {
|
||||
selectCaseModal.open({ attachments });
|
||||
}
|
||||
}, [actionId, agentIds, hasCasesPermissions, queryId, selectCaseModal]);
|
||||
}, [actionId, agentIds, alertAttachments, hasCasesPermissions, queryId, selectCaseModal]);
|
||||
|
||||
if (isIcon) {
|
||||
return (
|
||||
|
|
15
x-pack/plugins/osquery/public/common/contexts.tsx
Normal file
15
x-pack/plugins/osquery/public/common/contexts.tsx
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.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
|
||||
export interface AlertEcsData {
|
||||
_id: string;
|
||||
_index?: string;
|
||||
}
|
||||
|
||||
export const AlertAttachmentContext = React.createContext<AlertEcsData | null>(null);
|
|
@ -5,21 +5,35 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React, { lazy, Suspense } from 'react';
|
||||
import React, { lazy, Suspense, useMemo } from 'react';
|
||||
import ServicesWrapper from './services_wrapper';
|
||||
import type { ServicesWrapperProps } from './services_wrapper';
|
||||
import type { OsqueryActionProps } from './osquery_action';
|
||||
import type { AlertEcsData } from '../common/contexts';
|
||||
import { AlertAttachmentContext } from '../common/contexts';
|
||||
|
||||
export const getLazyOsqueryAction =
|
||||
(services: ServicesWrapperProps['services']) =>
|
||||
// eslint-disable-next-line react/display-name
|
||||
(services: ServicesWrapperProps['services']) => (props: OsqueryActionProps) => {
|
||||
(props: OsqueryActionProps & { ecsData?: AlertEcsData }) => {
|
||||
const OsqueryAction = lazy(() => import('./osquery_action'));
|
||||
|
||||
const { ecsData, ...restProps } = props;
|
||||
const renderAction = useMemo(() => {
|
||||
if (ecsData && ecsData?._id) {
|
||||
return (
|
||||
<AlertAttachmentContext.Provider value={ecsData}>
|
||||
<OsqueryAction {...restProps} />
|
||||
</AlertAttachmentContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
return <OsqueryAction {...restProps} />;
|
||||
}, [OsqueryAction, ecsData, restProps]);
|
||||
|
||||
return (
|
||||
<Suspense fallback={null}>
|
||||
<ServicesWrapper services={services}>
|
||||
<OsqueryAction {...props} />
|
||||
</ServicesWrapper>
|
||||
<ServicesWrapper services={services}>{renderAction}</ServicesWrapper>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -24,6 +24,7 @@ const OsqueryActionResultsComponent: React.FC<OsqueryActionResultsProps> = ({
|
|||
agentIds,
|
||||
ruleName,
|
||||
alertId,
|
||||
ecsData,
|
||||
}) => {
|
||||
const { data: actionsData } = useAllLiveQueries({
|
||||
filterQuery: { term: { alert_ids: alertId } },
|
||||
|
@ -49,6 +50,7 @@ const OsqueryActionResultsComponent: React.FC<OsqueryActionResultsProps> = ({
|
|||
startDate={startDate}
|
||||
ruleName={ruleName}
|
||||
agentIds={agentIds}
|
||||
ecsData={ecsData}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
|
|
|
@ -41,6 +41,9 @@ const defaultProps = {
|
|||
actionId: 'test-action-id',
|
||||
startDate: DETAILS_TIMESTAMP,
|
||||
queryId: '',
|
||||
ecsData: {
|
||||
_id: 'test',
|
||||
},
|
||||
};
|
||||
const mockKibana = (permissionType: unknown = defaultPermissions) => {
|
||||
const mockedKibana = getMockedKibanaConfig(permissionType);
|
||||
|
|
|
@ -13,6 +13,7 @@ import type { OsqueryActionResultsProps } from './types';
|
|||
import { useLiveQueryDetails } from '../../actions/use_live_query_details';
|
||||
import { ATTACHED_QUERY } from '../../agents/translations';
|
||||
import { PackQueriesStatusTable } from '../../live_queries/form/pack_queries_status_table';
|
||||
import { AlertAttachmentContext } from '../../common/contexts';
|
||||
|
||||
interface OsqueryResultProps extends Omit<OsqueryActionResultsProps, 'alertId'> {
|
||||
actionId: string;
|
||||
|
@ -21,13 +22,13 @@ interface OsqueryResultProps extends Omit<OsqueryActionResultsProps, 'alertId'>
|
|||
}
|
||||
|
||||
export const OsqueryResult = React.memo<OsqueryResultProps>(
|
||||
({ actionId, ruleName, agentIds, startDate }) => {
|
||||
({ actionId, ruleName, agentIds, startDate, ecsData }) => {
|
||||
const { data } = useLiveQueryDetails({
|
||||
actionId,
|
||||
});
|
||||
|
||||
return (
|
||||
<div>
|
||||
<AlertAttachmentContext.Provider value={ecsData}>
|
||||
<EuiSpacer size="s" />
|
||||
<EuiComment
|
||||
username={ruleName && ruleName[0]}
|
||||
|
@ -45,7 +46,7 @@ export const OsqueryResult = React.memo<OsqueryResultProps>(
|
|||
/>
|
||||
</EuiComment>
|
||||
<EuiSpacer size="s" />
|
||||
</div>
|
||||
</AlertAttachmentContext.Provider>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -36,6 +36,9 @@ const defaultProps = {
|
|||
ruleName: ['Test-rule'],
|
||||
ruleActions: [{ action_type_id: 'action1' }, { action_type_id: 'action2' }],
|
||||
alertId: 'test-alert-id',
|
||||
ecsData: {
|
||||
_id: 'test',
|
||||
},
|
||||
};
|
||||
|
||||
const defaultPermissions = {
|
||||
|
|
|
@ -52,6 +52,7 @@ export const getMockedKibanaConfig = (permissionType: unknown) =>
|
|||
update: true,
|
||||
push: true,
|
||||
})),
|
||||
getRuleIdFromEvent: jest.fn(),
|
||||
},
|
||||
ui: {
|
||||
getCasesContext: jest.fn().mockImplementation(() => mockCasesContext),
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { AlertEcsData } from '../../common/contexts';
|
||||
|
||||
export interface OsqueryActionResultsProps {
|
||||
agentIds?: string[];
|
||||
ruleName?: string[];
|
||||
alertId: string;
|
||||
ecsData: AlertEcsData;
|
||||
}
|
||||
|
|
|
@ -393,6 +393,7 @@ const EventDetailsComponent: React.FC<Props> = ({
|
|||
|
||||
const osqueryTab = useOsqueryTab({
|
||||
rawEventData: rawEventData as AlertRawEventData,
|
||||
...(detailsEcsData !== null ? { ecsData: detailsEcsData } : {}),
|
||||
});
|
||||
|
||||
const tabs = useMemo(() => {
|
||||
|
|
|
@ -15,6 +15,7 @@ import {
|
|||
import React, { useMemo } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { FormattedMessage } from '@kbn/i18n-react';
|
||||
import type { Ecs } from '../../../../common/ecs';
|
||||
import { PERMISSION_DENIED } from '../../../detection_engine/rule_response_actions/osquery/translations';
|
||||
import { expandDottedObject } from '../../../../common/utils/expand_dotted';
|
||||
import { RESPONSE_ACTION_TYPES } from '../../../../common/detection_engine/rule_response_actions/schemas';
|
||||
|
@ -59,7 +60,13 @@ interface ExpandedEventFieldsObject {
|
|||
};
|
||||
}
|
||||
|
||||
export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventData }) => {
|
||||
export const useOsqueryTab = ({
|
||||
rawEventData,
|
||||
ecsData,
|
||||
}: {
|
||||
rawEventData?: AlertRawEventData;
|
||||
ecsData?: Ecs;
|
||||
}) => {
|
||||
const {
|
||||
services: { osquery, application },
|
||||
} = useKibana();
|
||||
|
@ -87,7 +94,7 @@ export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventDa
|
|||
[]
|
||||
);
|
||||
|
||||
if (!osquery || !rawEventData || !responseActionsEnabled) {
|
||||
if (!osquery || !rawEventData || !responseActionsEnabled || !ecsData) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -137,7 +144,12 @@ export const useOsqueryTab = ({ rawEventData }: { rawEventData?: AlertRawEventDa
|
|||
{!application?.capabilities?.osquery?.read ? (
|
||||
emptyPrompt
|
||||
) : (
|
||||
<OsqueryResults agentIds={agentIds} ruleName={ruleName} alertId={alertId} />
|
||||
<OsqueryResults
|
||||
agentIds={agentIds}
|
||||
ruleName={ruleName}
|
||||
alertId={alertId}
|
||||
ecsData={ecsData}
|
||||
/>
|
||||
)}
|
||||
</TabContentWrapper>
|
||||
</>
|
||||
|
|
|
@ -283,6 +283,7 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps & PropsFromRedux
|
|||
agentId={agentId}
|
||||
defaultValues={alertId ? { alertIds: [alertId] } : undefined}
|
||||
onClose={handleOnOsqueryClick}
|
||||
ecsData={ecsRowData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { EuiFlyout, EuiFlyoutFooter, EuiFlyoutBody, EuiFlyoutHeader, EuiTitle } from '@elastic/eui';
|
||||
import type { Ecs } from '../../../../common/ecs';
|
||||
import { useKibana } from '../../../common/lib/kibana';
|
||||
import { OsqueryEventDetailsFooter } from './osquery_flyout_footer';
|
||||
import { ACTION_OSQUERY } from './translations';
|
||||
|
@ -20,11 +21,14 @@ export interface OsqueryFlyoutProps {
|
|||
agentId?: string;
|
||||
defaultValues?: {};
|
||||
onClose: () => void;
|
||||
ecsData?: Ecs;
|
||||
}
|
||||
export const OsqueryFlyoutComponent: React.FC<OsqueryFlyoutProps> = ({
|
||||
|
||||
const OsqueryFlyoutComponent: React.FC<OsqueryFlyoutProps> = ({
|
||||
agentId,
|
||||
defaultValues,
|
||||
onClose,
|
||||
ecsData,
|
||||
}) => {
|
||||
const {
|
||||
services: { osquery },
|
||||
|
@ -49,6 +53,7 @@ export const OsqueryFlyoutComponent: React.FC<OsqueryFlyoutProps> = ({
|
|||
agentId={agentId}
|
||||
formType="steps"
|
||||
defaultValues={defaultValues}
|
||||
ecsData={ecsData}
|
||||
/>
|
||||
</OsqueryActionWrapper>
|
||||
</EuiFlyoutBody>
|
||||
|
|
|
@ -195,6 +195,7 @@ export const FlyoutFooterComponent = React.memo(
|
|||
agentId={isOsqueryFlyoutOpenWithAgentId}
|
||||
defaultValues={alertId ? { alertIds: [alertId] } : undefined}
|
||||
onClose={closeOsqueryFlyout}
|
||||
ecsData={detailsEcsData}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue