[Osquery] [PoC] Add alert attachment along with Osquery Results to Cases (#143674)

This commit is contained in:
Tomasz Ciecierski 2022-11-03 09:41:57 +01:00 committed by GitHub
parent 9e19d09e37
commit cf152eae23
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 96 additions and 14 deletions

View file

@ -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 (

View 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);

View file

@ -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>
);
};

View file

@ -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}
/>
);
})}

View file

@ -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);

View file

@ -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>
);
}
);

View file

@ -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 = {

View file

@ -52,6 +52,7 @@ export const getMockedKibanaConfig = (permissionType: unknown) =>
update: true,
push: true,
})),
getRuleIdFromEvent: jest.fn(),
},
ui: {
getCasesContext: jest.fn().mockImplementation(() => mockCasesContext),

View file

@ -5,8 +5,11 @@
* 2.0.
*/
import type { AlertEcsData } from '../../common/contexts';
export interface OsqueryActionResultsProps {
agentIds?: string[];
ruleName?: string[];
alertId: string;
ecsData: AlertEcsData;
}

View file

@ -393,6 +393,7 @@ const EventDetailsComponent: React.FC<Props> = ({
const osqueryTab = useOsqueryTab({
rawEventData: rawEventData as AlertRawEventData,
...(detailsEcsData !== null ? { ecsData: detailsEcsData } : {}),
});
const tabs = useMemo(() => {

View file

@ -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>
</>

View file

@ -283,6 +283,7 @@ const AlertContextMenuComponent: React.FC<AlertContextMenuProps & PropsFromRedux
agentId={agentId}
defaultValues={alertId ? { alertIds: [alertId] } : undefined}
onClose={handleOnOsqueryClick}
ecsData={ecsRowData}
/>
)}
</>

View file

@ -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>

View file

@ -195,6 +195,7 @@ export const FlyoutFooterComponent = React.memo(
agentId={isOsqueryFlyoutOpenWithAgentId}
defaultValues={alertId ? { alertIds: [alertId] } : undefined}
onClose={closeOsqueryFlyout}
ecsData={detailsEcsData}
/>
)}
</>