Skip to content

Commit

Permalink
[Defend Workflows] Common response actions tab in alert Flyout (#155362)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomsonpl authored May 24, 2023
1 parent eff18a4 commit 8f004fd
Show file tree
Hide file tree
Showing 61 changed files with 1,279 additions and 319 deletions.
7 changes: 6 additions & 1 deletion x-pack/plugins/osquery/common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,12 @@ export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000;
export const DEFAULT_DARK_MODE = 'theme:darkMode';
export const OSQUERY_INTEGRATION_NAME = 'osquery_manager';
export const BASE_PATH = '/app/osquery';
export const ACTIONS_INDEX = `.logs-${OSQUERY_INTEGRATION_NAME}.actions`;

export const OSQUERY_LOGS_BASE = `.logs-${OSQUERY_INTEGRATION_NAME}`;
export const ACTIONS_INDEX = `${OSQUERY_LOGS_BASE}.actions`;
export const RESULTS_INDEX = `${OSQUERY_LOGS_BASE}.results`;
export const OSQUERY_ACTIONS_INDEX = `${ACTIONS_INDEX}-*`;

export const ACTION_RESPONSES_INDEX = `.logs-${OSQUERY_INTEGRATION_NAME}.action.responses`;

export const DEFAULT_PLATFORM = 'linux,windows,darwin';
Expand Down
26 changes: 26 additions & 0 deletions x-pack/plugins/osquery/common/types/osquery_action.ts
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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

export interface LogsOsqueryAction {
'@timestamp': string;
action_id: string;
alert_ids: string[];
expiration: string;
input_type: 'osquery';
queries: Array<{
action_id: string;
id: string;
query: string;
agents: string[];
ecs_mapping?: unknown;
version?: string;
platform?: string;
saved_query_id?: string;
expiration?: string;
}>;
type: 'INPUT_ACTION';
}
7 changes: 7 additions & 0 deletions x-pack/plugins/osquery/public/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { Storage } from '@kbn/kibana-utils-plugin/public';
import { useAllLiveQueries } from './actions/use_all_live_queries';
import { getLazyOsqueryResponseActionTypeForm } from './shared_components/lazy_osquery_action_params_form';
import { useFetchStatus } from './fleet_integration/use_fetch_status';
import { getLazyOsqueryResult } from './shared_components/lazy_osquery_result';
import { getLazyOsqueryResults } from './shared_components/lazy_osquery_results';
import type {
OsqueryPluginSetup,
Expand Down Expand Up @@ -122,6 +123,12 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
...core,
...plugins,
}),
OsqueryResult: getLazyOsqueryResult({
...core,
...plugins,
storage: this.storage,
kibanaVersion: this.kibanaVersion,
}),
OsqueryResults: getLazyOsqueryResults({
...core,
...plugins,
Expand Down
32 changes: 32 additions & 0 deletions x-pack/plugins/osquery/public/routes/components/empty_prompt.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* 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';
import { EuiCode, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { PERMISSION_DENIED } from '../../shared_components/osquery_action/translations';

const EmptyPromptComponent = () => (
<EuiEmptyPrompt
iconType="logoOsquery"
title={<h2>{PERMISSION_DENIED}</h2>}
titleSize="xs"
body={
<FormattedMessage
id="xpack.osquery.results.permissionDenied"
defaultMessage="To access these results, ask your administrator for {osquery} Kibana
privileges."
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
values={{
osquery: <EuiCode>osquery</EuiCode>,
}}
/>
}
/>
);

export const EmptyPrompt = React.memo(EmptyPromptComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@
*/

import React, { lazy, Suspense } from 'react';
import { EuiCode, EuiEmptyPrompt } from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n-react';
import { OsqueryIcon } from '../../components/osquery_icon';
import { useKibana } from '../../common/lib/kibana';
import type { ServicesWrapperProps } from '../services_wrapper';
import ServicesWrapper from '../services_wrapper';
import { PERMISSION_DENIED } from '../osquery_action/translations';
import { EmptyPrompt } from '../../routes/components/empty_prompt';

export interface IExternalReferenceMetaDataProps {
externalReferenceMetadata: {
Expand All @@ -35,24 +32,7 @@ export const getLazyExternalContent =
} = useKibana();

if (!osquery.read) {
return (
<EuiEmptyPrompt
icon={<OsqueryIcon />}
title={<h2>{PERMISSION_DENIED}</h2>}
titleSize="xs"
body={
<FormattedMessage
id="xpack.osquery.cases.permissionDenied"
defaultMessage=" To access these results, ask your administrator for {osquery} Kibana
privileges."
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
values={{
osquery: <EuiCode>osquery</EuiCode>,
}}
/>
}
/>
);
return <EmptyPrompt />;
}

return (
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/osquery/public/shared_components/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
* 2.0.
*/

export { getLazyOsqueryResult } from './lazy_osquery_result';
export { getLazyOsqueryResults } from './lazy_osquery_results';
export { getLazyOsqueryAction } from './lazy_osquery_action';
export { getLazyLiveQueryField } from './lazy_live_query_field';
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
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

import React, { lazy, Suspense } from 'react';
import type { OsqueryActionResultProps } from './osquery_results/types';
import type { StartServices } from '../types';

interface BigServices extends StartServices {
kibanaVersion: string;
storage: unknown;
}

const OsqueryResult = lazy(() => import('./osquery_results/osquery_result_wrapper'));

export const getLazyOsqueryResult =
// eslint-disable-next-line react/display-name
(services: BigServices) => (props: OsqueryActionResultProps) =>
(
<Suspense fallback={null}>
<OsqueryResult services={services} {...props} />
</Suspense>
);
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@
*/

import React, { lazy, Suspense } from 'react';
import type { OsqueryActionResultsProps } from './osquery_results/types';
import type { StartServices } from '../types';
import type { OsqueryActionResultsProps } from './osquery_results/types';

interface BigServices extends StartServices {
kibanaVersion: string;
storage: unknown;
}

const OsqueryResults = lazy(() => import('./osquery_results'));
const OsqueryResults = lazy(() => import('./osquery_results/osquery_results'));

export const getLazyOsqueryResults =
// eslint-disable-next-line react/display-name
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { queryClient } from '../../query_client';
import { useKibana } from '../../common/lib/kibana';
import * as useLiveQueryDetails from '../../actions/use_live_query_details';
import { PERMISSION_DENIED } from '../osquery_action/translations';
import { OsqueryResult } from './osquery_result';
import { OsqueryActionResult } from './osquery_result_wrapper';
import {
defaultLiveQueryDetails,
DETAILS_ID,
Expand All @@ -29,6 +29,7 @@ const useKibanaMock = useKibana as jest.MockedFunction<typeof useKibana>;

const defaultPermissions = {
osquery: {
read: true,
runSavedQueries: true,
readSavedQueries: true,
},
Expand Down Expand Up @@ -67,7 +68,7 @@ describe('Osquery Results', () => {

it('return results table', async () => {
const { getByText, queryByText, getByTestId } = renderWithContext(
<OsqueryResult {...defaultProps} />
<OsqueryActionResult {...defaultProps} />
);
expect(queryByText(PERMISSION_DENIED)).not.toBeInTheDocument();
expect(getByTestId('osquery-results-comment'));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
/*
* 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 { EuiComment, EuiErrorBoundary, EuiSpacer } from '@elastic/eui';
import React, { useState, useEffect } from 'react';
import { FormattedRelative } from '@kbn/i18n-react';

import type { CoreStart } from '@kbn/core-lifecycle-browser';
import { KibanaContextProvider, KibanaThemeProvider } from '@kbn/kibana-react-plugin/public';
import { QueryClientProvider } from '@tanstack/react-query';
import { EmptyPrompt } from '../../routes/components/empty_prompt';
import { useKibana } from '../../common/lib/kibana';
import type { StartPlugins } from '../../types';
import { queryClient } from '../../query_client';
import { AlertAttachmentContext } from '../../common/contexts';
import { PackQueriesStatusTable } from '../../live_queries/form/pack_queries_status_table';
import { ATTACHED_QUERY } from '../../agents/translations';
import { useLiveQueryDetails } from '../../actions/use_live_query_details';
import type { OsqueryActionResultProps } from './types';

const OsqueryResultComponent = React.memo<OsqueryActionResultProps>(
({ actionId, ruleName, startDate, ecsData }) => {
const { read } = useKibana().services.application.capabilities.osquery;

const [isLive, setIsLive] = useState(false);
const { data } = useLiveQueryDetails({
actionId,
isLive,
skip: !read,
});

useEffect(() => {
setIsLive(() => !(data?.status === 'completed'));
}, [data?.status]);

return (
<AlertAttachmentContext.Provider value={ecsData}>
<EuiSpacer size="s" />
<EuiComment
username={ruleName && ruleName[0]}
timestamp={<FormattedRelative value={startDate} />}
event={ATTACHED_QUERY}
data-test-subj={'osquery-results-comment'}
>
{!read ? (
<EmptyPrompt />
) : (
<PackQueriesStatusTable
actionId={actionId}
data={data?.queries}
startDate={data?.['@timestamp']}
expirationDate={data?.expiration}
agentIds={data?.agents}
/>
)}
</EuiComment>
<EuiSpacer size="s" />
</AlertAttachmentContext.Provider>
);
}
);

export const OsqueryActionResult = React.memo(OsqueryResultComponent);
type OsqueryActionResultsWrapperProps = {
services: CoreStart & StartPlugins;
} & OsqueryActionResultProps;

const OsqueryActionResultWrapperComponent: React.FC<OsqueryActionResultsWrapperProps> = ({
services,
...restProps
}) => (
<KibanaThemeProvider theme$={services.theme.theme$}>
<KibanaContextProvider services={services}>
<EuiErrorBoundary>
<QueryClientProvider client={queryClient}>
<OsqueryActionResult {...restProps} />
</QueryClientProvider>
</EuiErrorBoundary>
</KibanaContextProvider>
</KibanaThemeProvider>
);

const OsqueryActionResultWrapper = React.memo(OsqueryActionResultWrapperComponent);

// eslint-disable-next-line import/no-default-export
export { OsqueryActionResultWrapper as default };
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { __IntlProvider as IntlProvider } from '@kbn/i18n-react';
import { render } from '@testing-library/react';
import { QueryClientProvider } from '@tanstack/react-query';

import { OsqueryActionResults } from '.';
import { OsqueryActionResults } from './osquery_results';
import { queryClient } from '../../query_client';
import { useKibana } from '../../common/lib/kibana';
import * as useLiveQueryDetails from '../../actions/use_live_query_details';
Expand Down Expand Up @@ -52,6 +52,7 @@ const defaultProps: OsqueryActionResultsProps = {

const defaultPermissions = {
osquery: {
read: true,
runSavedQueries: false,
readSavedQueries: false,
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ import React from 'react';
import { QueryClientProvider } from '@tanstack/react-query';
import type { CoreStart } from '@kbn/core/public';

import { KibanaContextProvider } from '../../common/lib/kibana';
import { EmptyPrompt } from '../../routes/components/empty_prompt';
import { KibanaContextProvider, useKibana } from '../../common/lib/kibana';

import { queryClient } from '../../query_client';
import { KibanaThemeProvider } from '../../shared_imports';
Expand All @@ -22,27 +23,33 @@ const OsqueryActionResultsComponent: React.FC<OsqueryActionResultsProps> = ({
ruleName,
actionItems,
ecsData,
}) => (
<div data-test-subj={'osquery-results'}>
{actionItems?.map((item) => {
const actionId = item.fields?.action_id?.[0];
const queryId = item.fields?.['queries.action_id']?.[0];
const startDate = item.fields?.['@timestamp'][0];
}) => {
const { read } = useKibana().services.application.capabilities.osquery;

return (
<OsqueryResult
key={actionId}
actionId={actionId}
queryId={queryId}
startDate={startDate}
ruleName={ruleName}
ecsData={ecsData}
/>
);
})}
<EuiSpacer size="s" />
</div>
);
return !read ? (
<EmptyPrompt />
) : (
<div data-test-subj={'osquery-results'}>
{actionItems?.map((item) => {
const actionId = item.fields?.action_id?.[0];
const queryId = item.fields?.['queries.action_id']?.[0];
const startDate = item.fields?.['@timestamp'][0];

return (
<OsqueryResult
key={actionId}
actionId={actionId}
queryId={queryId}
startDate={startDate}
ruleName={ruleName}
ecsData={ecsData}
/>
);
})}
<EuiSpacer size="s" />
</div>
);
};

export const OsqueryActionResults = React.memo(OsqueryActionResultsComponent);

Expand Down
Loading

0 comments on commit 8f004fd

Please sign in to comment.