mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Osquery] Add Osquery tab to node details tabs (#104272)
This commit is contained in:
parent
a9844db461
commit
9d0a7b8394
25 changed files with 377 additions and 192 deletions
|
@ -15,7 +15,7 @@
|
|||
"observability",
|
||||
"ruleRegistry"
|
||||
],
|
||||
"optionalPlugins": ["ml", "home", "embeddable"],
|
||||
"optionalPlugins": ["ml", "home", "embeddable", "osquery"],
|
||||
"server": true,
|
||||
"ui": true,
|
||||
"configPath": ["xpack", "infra"],
|
||||
|
|
|
@ -20,6 +20,7 @@ import { LogsTab } from './tabs/logs';
|
|||
import { ProcessesTab } from './tabs/processes';
|
||||
import { PropertiesTab } from './tabs/properties/index';
|
||||
import { AnomaliesTab } from './tabs/anomalies/anomalies';
|
||||
import { OsqueryTab } from './tabs/osquery';
|
||||
import { OVERLAY_Y_START, OVERLAY_BOTTOM_MARGIN } from './tabs/shared';
|
||||
import { useLinkProps } from '../../../../../hooks/use_link_props';
|
||||
import { getNodeDetailUrl } from '../../../../link_to';
|
||||
|
@ -45,7 +46,7 @@ export const NodeContextPopover = ({
|
|||
openAlertFlyout,
|
||||
}: Props) => {
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
const tabConfigs = [MetricsTab, LogsTab, ProcessesTab, PropertiesTab, AnomaliesTab];
|
||||
const tabConfigs = [MetricsTab, LogsTab, ProcessesTab, PropertiesTab, AnomaliesTab, OsqueryTab];
|
||||
const inventoryModel = findInventoryModel(nodeType);
|
||||
const nodeDetailFrom = currentTime - inventoryModel.metrics.defaultTimeRangeInSeconds * 1000;
|
||||
const uiCapabilities = useKibana().services.application?.capabilities;
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { EuiLoadingContent } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import React, { useContext, useMemo } from 'react';
|
||||
import { useKibanaContextForPlugin } from '../../../../../../../hooks/use_kibana';
|
||||
import { TabContent, TabProps } from '../shared';
|
||||
import { Source } from '../../../../../../../containers/metrics_source';
|
||||
import { findInventoryModel } from '../../../../../../../../common/inventory_models';
|
||||
import { InventoryItemType } from '../../../../../../../../common/inventory_models/types';
|
||||
import { useMetadata } from '../../../../../metric_detail/hooks/use_metadata';
|
||||
import { useWaffleTimeContext } from '../../../../hooks/use_waffle_time';
|
||||
|
||||
const TabComponent = (props: TabProps) => {
|
||||
const nodeId = props.node.id;
|
||||
const nodeType = props.nodeType as InventoryItemType;
|
||||
const inventoryModel = findInventoryModel(nodeType);
|
||||
const { sourceId } = useContext(Source.Context);
|
||||
const { currentTimeRange } = useWaffleTimeContext();
|
||||
const { loading, metadata } = useMetadata(
|
||||
nodeId,
|
||||
nodeType,
|
||||
inventoryModel.requiredMetrics,
|
||||
sourceId,
|
||||
currentTimeRange
|
||||
);
|
||||
const {
|
||||
services: { osquery },
|
||||
} = useKibanaContextForPlugin();
|
||||
// @ts-expect-error
|
||||
const OsqueryAction = osquery?.OsqueryAction;
|
||||
|
||||
// avoids component rerender when resizing the popover
|
||||
const content = useMemo(() => {
|
||||
// TODO: Add info when Osquery plugin is not available
|
||||
if (loading || !OsqueryAction) {
|
||||
return (
|
||||
<TabContent>
|
||||
<EuiLoadingContent lines={10} />
|
||||
</TabContent>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<TabContent>
|
||||
<OsqueryAction metadata={metadata} />
|
||||
</TabContent>
|
||||
);
|
||||
}, [OsqueryAction, loading, metadata]);
|
||||
|
||||
return content;
|
||||
};
|
||||
|
||||
export const OsqueryTab = {
|
||||
id: 'osquery',
|
||||
name: i18n.translate('xpack.infra.nodeDetails.tabs.osquery', {
|
||||
defaultMessage: 'Osquery',
|
||||
}),
|
||||
content: TabComponent,
|
||||
};
|
|
@ -23,6 +23,7 @@ import type {
|
|||
ObservabilityPublicSetup,
|
||||
ObservabilityPublicStart,
|
||||
} from '../../observability/public';
|
||||
// import type { OsqueryPluginStart } from '../../osquery/public';
|
||||
import type { SpacesPluginStart } from '../../spaces/public';
|
||||
import { MlPluginStart, MlPluginSetup } from '../../ml/public';
|
||||
import type { EmbeddableStart } from '../../../../src/plugins/embeddable/public';
|
||||
|
@ -50,6 +51,7 @@ export interface InfraClientStartDeps {
|
|||
usageCollection: UsageCollectionStart;
|
||||
ml: MlPluginStart;
|
||||
embeddable?: EmbeddableStart;
|
||||
osquery?: unknown; // OsqueryPluginStart;
|
||||
}
|
||||
|
||||
export type InfraClientCoreSetup = CoreSetup<InfraClientStartDeps, InfraClientStartExports>;
|
||||
|
|
|
@ -9,12 +9,14 @@
|
|||
"id": "osquery",
|
||||
"kibanaVersion": "kibana",
|
||||
"optionalPlugins": [
|
||||
"fleet",
|
||||
"home",
|
||||
"usageCollection",
|
||||
"lens"
|
||||
],
|
||||
"requiredBundles": [
|
||||
"esUiShared",
|
||||
"fleet",
|
||||
"kibanaUtils",
|
||||
"kibanaReact"
|
||||
],
|
||||
|
@ -24,7 +26,6 @@
|
|||
"dataEnhanced",
|
||||
"discover",
|
||||
"features",
|
||||
"fleet",
|
||||
"navigation",
|
||||
"triggersActionsUi",
|
||||
"security"
|
||||
|
|
|
@ -12,11 +12,12 @@ import { useKibana } from '../common/lib/kibana';
|
|||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
interface UseAgentPolicy {
|
||||
policyId: string;
|
||||
policyId?: string;
|
||||
silent?: boolean;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useAgentPolicy = ({ policyId, skip }: UseAgentPolicy) => {
|
||||
export const useAgentPolicy = ({ policyId, skip, silent }: UseAgentPolicy) => {
|
||||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
|
||||
|
@ -24,11 +25,12 @@ export const useAgentPolicy = ({ policyId, skip }: UseAgentPolicy) => {
|
|||
['agentPolicy', { policyId }],
|
||||
() => http.get(`/internal/osquery/fleet_wrapper/agent_policies/${policyId}`),
|
||||
{
|
||||
enabled: !skip,
|
||||
enabled: !!(policyId && !skip),
|
||||
keepPreviousData: true,
|
||||
select: (response) => response.item,
|
||||
onSuccess: () => setErrorToast(),
|
||||
onError: (error: Error) =>
|
||||
!silent &&
|
||||
setErrorToast(error, {
|
||||
title: i18n.translate('xpack.osquery.agent_policy_details.fetchError', {
|
||||
defaultMessage: 'Error while fetching agent policy details',
|
||||
|
|
|
@ -19,7 +19,7 @@ interface AgentIdToNameProps {
|
|||
|
||||
const AgentIdToNameComponent: React.FC<AgentIdToNameProps> = ({ agentId }) => {
|
||||
const getUrlForApp = useKibana().services.application.getUrlForApp;
|
||||
const { data } = useAgentDetails({ agentId });
|
||||
const { data } = useAgentDetails({ agentId, skip: !agentId });
|
||||
|
||||
return (
|
||||
<EuiLink
|
||||
|
@ -29,7 +29,7 @@ const AgentIdToNameComponent: React.FC<AgentIdToNameProps> = ({ agentId }) => {
|
|||
})}
|
||||
target="_blank"
|
||||
>
|
||||
{data?.item.local_metadata.host.name ?? agentId}
|
||||
{data?.local_metadata.host.name ?? agentId}
|
||||
</EuiLink>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -13,19 +13,24 @@ import { useErrorToast } from '../common/hooks/use_error_toast';
|
|||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
interface UseAgentDetails {
|
||||
agentId: string;
|
||||
agentId?: string;
|
||||
silent?: boolean;
|
||||
skip?: boolean;
|
||||
}
|
||||
|
||||
export const useAgentDetails = ({ agentId }: UseAgentDetails) => {
|
||||
export const useAgentDetails = ({ agentId, silent, skip }: UseAgentDetails) => {
|
||||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
return useQuery<GetOneAgentResponse>(
|
||||
return useQuery<GetOneAgentResponse, unknown, GetOneAgentResponse['item']>(
|
||||
['agentDetails', agentId],
|
||||
() => http.get(`/internal/osquery/fleet_wrapper/agents/${agentId}`),
|
||||
{
|
||||
enabled: agentId.length > 0,
|
||||
enabled: !skip,
|
||||
retry: false,
|
||||
select: (response: GetOneAgentResponse) => response?.item,
|
||||
onSuccess: () => setErrorToast(),
|
||||
onError: (error) =>
|
||||
!silent &&
|
||||
setErrorToast(error as Error, {
|
||||
title: i18n.translate('xpack.osquery.agentDetails.fetchError', {
|
||||
defaultMessage: 'Error while fetching agent details',
|
||||
|
|
|
@ -11,7 +11,7 @@ import { useQuery } from 'react-query';
|
|||
import { useKibana } from '../lib/kibana';
|
||||
import { useErrorToast } from './use_error_toast';
|
||||
|
||||
export const useOsqueryIntegration = () => {
|
||||
export const useOsqueryIntegrationStatus = () => {
|
||||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ import { INTEGRATIONS_PLUGIN_ID } from '../../../fleet/common';
|
|||
import { pagePathGetters } from '../../../fleet/public';
|
||||
|
||||
import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kibana';
|
||||
import { useOsqueryIntegration } from '../common/hooks';
|
||||
import { useOsqueryIntegrationStatus } from '../common/hooks';
|
||||
|
||||
const ManageIntegrationLinkComponent = () => {
|
||||
const {
|
||||
application: { getUrlForApp, navigateToApp },
|
||||
} = useKibana().services;
|
||||
const { data: osqueryIntegration } = useOsqueryIntegration();
|
||||
const { data: osqueryIntegration } = useOsqueryIntegrationStatus();
|
||||
|
||||
const integrationHref = useMemo(() => {
|
||||
if (osqueryIntegration) {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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 { EuiIcon, EuiIconProps } from '@elastic/eui';
|
||||
import OsqueryLogo from './osquery.svg';
|
||||
|
||||
export type OsqueryIconProps = Omit<EuiIconProps, 'type'>;
|
||||
|
||||
const OsqueryIconComponent: React.FC<OsqueryIconProps> = (props) => (
|
||||
<EuiIcon size="xl" type={OsqueryLogo} {...props} />
|
||||
);
|
||||
|
||||
export const OsqueryIcon = React.memo(OsqueryIconComponent);
|
13
x-pack/plugins/osquery/public/components/osquery_icon/osquery.svg
Executable file
13
x-pack/plugins/osquery/public/components/osquery_icon/osquery.svg
Executable file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg width="256px" height="255px" viewBox="0 0 256 255" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid">
|
||||
<g>
|
||||
<path d="M255.214617,0.257580247 L255.214617,63.993679 L191.609679,127.598617 L191.609679,63.7297778 L255.214617,0.257580247" fill="#A596FF"></path>
|
||||
<path d="M128.006321,0.257580247 L128.006321,63.993679 L191.611259,127.598617 L191.611259,63.7297778 L128.006321,0.257580247" fill="#000000"></path>
|
||||
<path d="M255.345778,254.803753 L191.609679,254.803753 L128.004741,191.198815 L191.872,191.198815 L255.345778,254.803753" fill="#A596FF"></path>
|
||||
<path d="M255.345778,127.595457 L191.609679,127.595457 L128.004741,191.200395 L191.872,191.200395 L255.345778,127.595457" fill="#000000"></path>
|
||||
<path d="M0.801185185,254.936494 L0.801185185,191.198815 L64.4061235,127.593877 L64.4061235,191.462716 L0.801185185,254.936494" fill="#A596FF"></path>
|
||||
<path d="M128.009481,254.936494 L128.009481,191.198815 L64.4045432,127.593877 L64.4045432,191.462716 L128.009481,254.936494" fill="#000000"></path>
|
||||
<path d="M0.671604938,0.385580247 L64.4077037,0.385580247 L128.012642,63.9905185 L64.1453827,63.9905185 L0.671604938,0.385580247" fill="#A596FF"></path>
|
||||
<path d="M0.671604938,127.593877 L64.4077037,127.593877 L128.012642,63.9889383 L64.1453827,63.9889383 L0.671604938,127.593877" fill="#000000"></path>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 1.4 KiB |
|
@ -28,13 +28,13 @@ interface OsqueryEditorProps {
|
|||
|
||||
const OsqueryEditorComponent: React.FC<OsqueryEditorProps> = ({
|
||||
defaultValue,
|
||||
disabled,
|
||||
// disabled,
|
||||
onChange,
|
||||
}) => (
|
||||
<EuiCodeEditor
|
||||
value={defaultValue}
|
||||
mode="osquery"
|
||||
isReadOnly={disabled}
|
||||
// isReadOnly={disabled}
|
||||
theme="tomorrow"
|
||||
onChange={onChange}
|
||||
name="osquery_editor"
|
||||
|
|
|
@ -16,12 +16,10 @@ import {
|
|||
agentPolicyRouteService,
|
||||
AgentPolicy,
|
||||
PLUGIN_ID,
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
NewPackagePolicy,
|
||||
} from '../../../fleet/common';
|
||||
import {
|
||||
pagePathGetters,
|
||||
CreatePackagePolicyRouteState,
|
||||
PackagePolicyCreateExtensionComponentProps,
|
||||
PackagePolicyEditExtensionComponentProps,
|
||||
} from '../../../fleet/public';
|
||||
|
@ -49,7 +47,7 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
|
|||
http,
|
||||
} = useKibana().services;
|
||||
const { state: locationState } = useLocation();
|
||||
const { replace, go } = useHistory();
|
||||
const { go } = useHistory();
|
||||
|
||||
const agentsLinkHref = useMemo(() => {
|
||||
if (!policy?.policy_id) return '#';
|
||||
|
@ -126,27 +124,28 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
|
|||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!editMode) {
|
||||
replace({
|
||||
state: {
|
||||
onSaveNavigateTo: (newPackagePolicy) => [
|
||||
INTEGRATIONS_PLUGIN_ID,
|
||||
{
|
||||
path:
|
||||
'#' +
|
||||
pagePathGetters.integration_policy_edit({
|
||||
packagePolicyId: newPackagePolicy.id,
|
||||
})[1],
|
||||
state: {
|
||||
forceRefresh: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
} as CreatePackagePolicyRouteState,
|
||||
});
|
||||
}
|
||||
}, [editMode, replace]);
|
||||
// TODO: Find a better solution
|
||||
// useEffect(() => {
|
||||
// if (!editMode) {
|
||||
// replace({
|
||||
// state: {
|
||||
// onSaveNavigateTo: (newPackagePolicy) => [
|
||||
// INTEGRATIONS_PLUGIN_ID,
|
||||
// {
|
||||
// path:
|
||||
// '#' +
|
||||
// pagePathGetters.integration_policy_edit({
|
||||
// packagePolicyId: newPackagePolicy.id,
|
||||
// })[1],
|
||||
// state: {
|
||||
// forceRefresh: true,
|
||||
// },
|
||||
// },
|
||||
// ],
|
||||
// } as CreatePackagePolicyRouteState,
|
||||
// });
|
||||
// }
|
||||
// }, [editMode, replace]);
|
||||
|
||||
const scheduledQueryGroupTableData = useMemo(() => {
|
||||
const policyWithoutEmptyQueries = produce<
|
||||
|
|
|
@ -37,15 +37,15 @@ export const MAX_QUERY_LENGTH = 2000;
|
|||
const GhostFormField = () => <></>;
|
||||
|
||||
interface LiveQueryFormProps {
|
||||
agentId?: string | undefined;
|
||||
defaultValue?: Partial<FormData> | undefined;
|
||||
onSuccess?: () => void;
|
||||
singleAgentMode?: boolean;
|
||||
}
|
||||
|
||||
const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
||||
agentId,
|
||||
defaultValue,
|
||||
onSuccess,
|
||||
singleAgentMode,
|
||||
}) => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
const { http } = useKibana().services;
|
||||
|
@ -55,14 +55,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
const handleShowSaveQueryFlout = useCallback(() => setShowSavedQueryFlyout(true), []);
|
||||
const handleCloseSaveQueryFlout = useCallback(() => setShowSavedQueryFlyout(false), []);
|
||||
|
||||
const {
|
||||
data,
|
||||
isLoading,
|
||||
mutateAsync,
|
||||
isError,
|
||||
isSuccess,
|
||||
// error
|
||||
} = useMutation(
|
||||
const { data, isLoading, mutateAsync, isError, isSuccess } = useMutation(
|
||||
(payload: Record<string, unknown>) =>
|
||||
http.post('/internal/osquery/action', {
|
||||
body: JSON.stringify(payload),
|
||||
|
@ -173,7 +166,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
/>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
{!agentId && (
|
||||
{!singleAgentMode && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
disabled={
|
||||
|
@ -203,13 +196,13 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
</>
|
||||
),
|
||||
[
|
||||
agentId,
|
||||
agentSelected,
|
||||
permissions.writeSavedQueries,
|
||||
handleShowSaveQueryFlout,
|
||||
queryComponentProps,
|
||||
queryValueProvided,
|
||||
resultsStatus,
|
||||
singleAgentMode,
|
||||
submit,
|
||||
]
|
||||
);
|
||||
|
@ -262,7 +255,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
|
||||
return (
|
||||
<>
|
||||
<Form form={form}>{agentId ? singleAgentForm : <EuiSteps steps={formSteps} />}</Form>
|
||||
<Form form={form}>{singleAgentMode ? singleAgentForm : <EuiSteps steps={formSteps} />}</Form>
|
||||
{showSavedQueryFlyout ? (
|
||||
<SavedQueryFlyout
|
||||
onClose={handleCloseSaveQueryFlout}
|
||||
|
|
|
@ -5,18 +5,69 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiCode, EuiLoadingContent, EuiEmptyPrompt } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { LiveQueryForm } from './form';
|
||||
import { FormData } from '../shared_imports';
|
||||
import { useActionResultsPrivileges } from '../action_results/use_action_privileges';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||
import { OsqueryIcon } from '../components/osquery_icon';
|
||||
|
||||
interface LiveQueryProps {
|
||||
defaultValue?: Partial<FormData> | undefined;
|
||||
agentId?: string;
|
||||
agentPolicyId?: string;
|
||||
onSuccess?: () => void;
|
||||
query?: string;
|
||||
}
|
||||
|
||||
const LiveQueryComponent: React.FC<LiveQueryProps> = ({ defaultValue, onSuccess }) => (
|
||||
<LiveQueryForm defaultValue={defaultValue} onSuccess={onSuccess} />
|
||||
);
|
||||
const LiveQueryComponent: React.FC<LiveQueryProps> = ({
|
||||
agentId,
|
||||
agentPolicyId,
|
||||
onSuccess,
|
||||
query,
|
||||
}) => {
|
||||
const { data: hasActionResultsPrivileges, isFetched } = useActionResultsPrivileges();
|
||||
|
||||
const defaultValue = useMemo(() => {
|
||||
if (agentId || agentPolicyId || query) {
|
||||
return {
|
||||
agentSelection: {
|
||||
allAgentsSelected: false,
|
||||
agents: agentId ? [agentId] : [],
|
||||
platformsSelected: [],
|
||||
policiesSelected: agentPolicyId ? [agentPolicyId] : [],
|
||||
},
|
||||
query,
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [agentId, agentPolicyId, query]);
|
||||
|
||||
if (!isFetched) {
|
||||
return <EuiLoadingContent lines={10} />;
|
||||
}
|
||||
|
||||
if (!hasActionResultsPrivileges) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<OsqueryIcon />}
|
||||
title={<h2>Permission denied</h2>}
|
||||
titleSize="xs"
|
||||
body={
|
||||
<p>
|
||||
To view query results, ask your administrator to update your user role to have index{' '}
|
||||
<EuiCode>read</EuiCode> privileges on the{' '}
|
||||
<EuiCode>logs-{OSQUERY_INTEGRATION_NAME}.result*</EuiCode> index.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<LiveQueryForm singleAgentMode={!!agentId} defaultValue={defaultValue} onSuccess={onSuccess} />
|
||||
);
|
||||
};
|
||||
|
||||
export const LiveQuery = React.memo(LiveQueryComponent);
|
||||
|
|
|
@ -5,6 +5,14 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { QueryClient } from 'react-query';
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
|
||||
import { QueryClient, setLogger } from 'react-query';
|
||||
|
||||
setLogger({
|
||||
log: () => {},
|
||||
warn: () => {},
|
||||
error: () => {},
|
||||
});
|
||||
|
||||
export const queryClient = new QueryClient();
|
||||
|
|
|
@ -203,6 +203,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
|
||||
const toolbarVisibility = useMemo(
|
||||
() => ({
|
||||
showStyleSelector: false,
|
||||
additionalControls: (
|
||||
<>
|
||||
<ViewResultsInDiscoverAction
|
||||
|
@ -247,8 +248,11 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
return (
|
||||
<EuiCallOut title="Missing privileges" color="danger" iconType="alert">
|
||||
<p>
|
||||
You're missing <EuiCode>read</EuiCode> privileges to read from
|
||||
<EuiCode>logs-{OSQUERY_INTEGRATION_NAME}.result*</EuiCode>.
|
||||
{'Your user role doesn’t have index read permissions on the '}
|
||||
<EuiCode>logs-{OSQUERY_INTEGRATION_NAME}.result*</EuiCode>
|
||||
{
|
||||
'index. Access to this index is required to view osquery results. Administrators can update role permissions in Stack Management > Roles.'
|
||||
}
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { EuiButtonEmpty, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useMemo } from 'react';
|
||||
import React, { useEffect, useMemo, useState } from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import qs from 'query-string';
|
||||
|
||||
|
@ -22,28 +22,20 @@ const NewLiveQueryPageComponent = () => {
|
|||
const { replace } = useHistory();
|
||||
const location = useLocation();
|
||||
const liveQueryListProps = useRouterNavigate('live_queries');
|
||||
const [initialQuery, setInitialQuery] = useState<string | undefined>(undefined);
|
||||
|
||||
const formDefaultValue = useMemo(() => {
|
||||
const agentPolicyId = useMemo(() => {
|
||||
const queryParams = qs.parse(location.search);
|
||||
|
||||
return queryParams?.agentPolicyId as string | undefined;
|
||||
}, [location.search]);
|
||||
|
||||
useEffect(() => {
|
||||
if (location.state?.form.query) {
|
||||
replace({ state: null });
|
||||
return { query: location.state?.form.query };
|
||||
setInitialQuery(location.state?.form.query);
|
||||
}
|
||||
|
||||
if (queryParams?.agentPolicyId) {
|
||||
return {
|
||||
agentSelection: {
|
||||
allAgentsSelected: false,
|
||||
agents: [],
|
||||
platformsSelected: [],
|
||||
policiesSelected: [queryParams?.agentPolicyId],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}, [location.search, location.state, replace]);
|
||||
}, [location.state?.form.query, replace]);
|
||||
|
||||
const LeftColumn = useMemo(
|
||||
() => (
|
||||
|
@ -74,7 +66,7 @@ const NewLiveQueryPageComponent = () => {
|
|||
|
||||
return (
|
||||
<WithHeaderLayout leftColumn={LeftColumn}>
|
||||
<LiveQuery defaultValue={formDefaultValue} />
|
||||
<LiveQuery agentPolicyId={agentPolicyId} query={initialQuery} />
|
||||
</WithHeaderLayout>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -12,14 +12,14 @@ import React, { useMemo } from 'react';
|
|||
import { WithHeaderLayout } from '../../../components/layouts';
|
||||
import { useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { ScheduledQueryGroupForm } from '../../../scheduled_query_groups/form';
|
||||
import { useOsqueryIntegration } from '../../../common/hooks';
|
||||
import { useOsqueryIntegrationStatus } from '../../../common/hooks';
|
||||
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
|
||||
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
|
||||
|
||||
const AddScheduledQueryGroupPageComponent = () => {
|
||||
useBreadcrumbs('scheduled_query_group_add');
|
||||
const scheduledQueryListProps = useRouterNavigate('scheduled_query_groups');
|
||||
const { data: osqueryIntegration } = useOsqueryIntegration();
|
||||
const { data: osqueryIntegration } = useOsqueryIntegrationStatus();
|
||||
|
||||
const packageInfo = useMemo(() => {
|
||||
if (!osqueryIntegration) return;
|
||||
|
|
|
@ -15,7 +15,6 @@ import {
|
|||
EuiToolTip,
|
||||
} from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
import {
|
||||
TypedLensByValueInput,
|
||||
|
@ -27,6 +26,20 @@ import { useKibana, isModifiedEvent, isLeftClickEvent } from '../common/lib/kiba
|
|||
import { PlatformIcons } from './queries/platforms';
|
||||
import { OsqueryManagerPackagePolicyInputStream } from '../../common/types';
|
||||
|
||||
const VIEW_IN_DISCOVER = i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View in Discover',
|
||||
}
|
||||
);
|
||||
|
||||
const VIEW_IN_LENS = i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View in Lens',
|
||||
}
|
||||
);
|
||||
|
||||
export enum ViewResultsActionButtonType {
|
||||
icon = 'icon',
|
||||
button = 'button',
|
||||
|
@ -182,33 +195,18 @@ const ViewResultsInLensActionComponent: React.FC<ViewResultsInDiscoverActionProp
|
|||
onClick={handleClick}
|
||||
disabled={!lensService?.canUseEditor()}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel"
|
||||
defaultMessage="View results in Lens"
|
||||
/>
|
||||
{VIEW_IN_LENS}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
content={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Lens',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiToolTip content={VIEW_IN_LENS}>
|
||||
<EuiButtonIcon
|
||||
iconType="lensApp"
|
||||
disabled={!lensService?.canUseEditor()}
|
||||
onClick={handleClick}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Lens',
|
||||
}
|
||||
)}
|
||||
aria-label={VIEW_IN_LENS}
|
||||
/>
|
||||
</EuiToolTip>
|
||||
);
|
||||
|
@ -271,33 +269,14 @@ const ViewResultsInDiscoverActionComponent: React.FC<ViewResultsInDiscoverAction
|
|||
if (buttonType === ViewResultsActionButtonType.button) {
|
||||
return (
|
||||
<EuiButtonEmpty size="xs" iconType="discoverApp" href={discoverUrl}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel"
|
||||
defaultMessage="View results in Discover"
|
||||
/>
|
||||
{VIEW_IN_DISCOVER}
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<EuiToolTip
|
||||
content={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Discover',
|
||||
}
|
||||
)}
|
||||
>
|
||||
<EuiButtonIcon
|
||||
iconType="discoverApp"
|
||||
href={discoverUrl}
|
||||
aria-label={i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel',
|
||||
{
|
||||
defaultMessage: 'View results in Discover',
|
||||
}
|
||||
)}
|
||||
/>
|
||||
<EuiToolTip content={VIEW_IN_DISCOVER}>
|
||||
<EuiButtonIcon iconType="discoverApp" href={discoverUrl} aria-label={VIEW_IN_DISCOVER} />
|
||||
</EuiToolTip>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -5,79 +5,129 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiErrorBoundary, EuiLoadingContent } from '@elastic/eui';
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { find } from 'lodash';
|
||||
import { EuiErrorBoundary, EuiLoadingContent, EuiEmptyPrompt, EuiCode } from '@elastic/eui';
|
||||
import React, { useMemo } from 'react';
|
||||
import { QueryClientProvider } from 'react-query';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
import { useAgentDetails } from '../../agents/use_agent_details';
|
||||
import { useAgentPolicy } from '../../agent_policies';
|
||||
import { KibanaContextProvider, useKibana } from '../../common/lib/kibana';
|
||||
|
||||
import { LiveQueryForm } from '../../live_queries/form';
|
||||
import { LiveQuery } from '../../live_queries';
|
||||
import { queryClient } from '../../query_client';
|
||||
import { OsqueryIcon } from '../../components/osquery_icon';
|
||||
|
||||
interface OsqueryActionProps {
|
||||
hostId?: string | undefined;
|
||||
metadata?: {
|
||||
info: {
|
||||
agent: { id: string };
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
const OsqueryActionComponent: React.FC<OsqueryActionProps> = ({ hostId }) => {
|
||||
const [agentId, setAgentId] = useState<string>();
|
||||
const { indexPatterns, search } = useKibana().services.data;
|
||||
|
||||
useEffect(() => {
|
||||
if (hostId) {
|
||||
const findAgent = async () => {
|
||||
const searchSource = await search.searchSource.create();
|
||||
const indexPattern = await indexPatterns.find('.fleet-agents');
|
||||
|
||||
searchSource.setField('index', indexPattern[0]);
|
||||
searchSource.setField('filter', [
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
negate: false,
|
||||
key: 'local_metadata.host.id',
|
||||
value: hostId,
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
'local_metadata.host.id': hostId,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
meta: {
|
||||
alias: null,
|
||||
disabled: false,
|
||||
negate: false,
|
||||
key: 'active',
|
||||
value: 'true',
|
||||
},
|
||||
query: {
|
||||
match_phrase: {
|
||||
active: 'true',
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const response = await searchSource.fetch$().toPromise();
|
||||
|
||||
if (response.rawResponse.hits.hits.length && response.rawResponse.hits.hits[0]._id) {
|
||||
setAgentId(response.rawResponse.hits.hits[0]._id);
|
||||
}
|
||||
};
|
||||
|
||||
findAgent();
|
||||
}
|
||||
const OsqueryActionComponent: React.FC<OsqueryActionProps> = ({ metadata }) => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
const agentId = metadata?.info?.agent?.id ?? undefined;
|
||||
const { data: agentData, isFetched: agentFetched } = useAgentDetails({
|
||||
agentId,
|
||||
silent: true,
|
||||
skip: !agentId,
|
||||
});
|
||||
const {
|
||||
data: agentPolicyData,
|
||||
isFetched: policyFetched,
|
||||
isError: policyError,
|
||||
isLoading: policyLoading,
|
||||
} = useAgentPolicy({
|
||||
policyId: agentData?.policy_id,
|
||||
skip: !agentData,
|
||||
silent: true,
|
||||
});
|
||||
|
||||
if (!agentId) {
|
||||
const osqueryAvailable = useMemo(() => {
|
||||
if (policyError) return false;
|
||||
|
||||
const osqueryPackageInstalled = find(agentPolicyData?.package_policies, [
|
||||
'package.name',
|
||||
OSQUERY_INTEGRATION_NAME,
|
||||
]);
|
||||
return osqueryPackageInstalled?.enabled;
|
||||
}, [agentPolicyData?.package_policies, policyError]);
|
||||
|
||||
if (!(permissions.runSavedQueries || permissions.writeLiveQueries)) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<OsqueryIcon />}
|
||||
title={<h2>Permissions denied</h2>}
|
||||
titleSize="xs"
|
||||
body={
|
||||
<p>
|
||||
To access this page, ask your administrator for <EuiCode>osquery</EuiCode> Kibana
|
||||
privileges.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!agentFetched) {
|
||||
return <EuiLoadingContent lines={10} />;
|
||||
}
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
<LiveQueryForm defaultValue={{ agentSelection: { agents: [agentId] } }} agentId={agentId} />
|
||||
);
|
||||
if (!agentId || (agentFetched && !agentData)) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<OsqueryIcon />}
|
||||
title={<h2>Osquery is not available</h2>}
|
||||
titleSize="xs"
|
||||
body={
|
||||
<p>
|
||||
An Elastic Agent is not installed on this host. To run queries, install Elastic Agent on
|
||||
the host, and then add the Osquery Manager integration to the agent policy in Fleet.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (!policyFetched && policyLoading) {
|
||||
return <EuiLoadingContent lines={10} />;
|
||||
}
|
||||
|
||||
if (!osqueryAvailable) {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<OsqueryIcon />}
|
||||
title={<h2>Osquery is not available</h2>}
|
||||
titleSize="xs"
|
||||
body={
|
||||
<p>
|
||||
The Osquery Manager integration is not added to the agent policy. To run queries on the
|
||||
host, add the Osquery Manager integration to the agent policy in Fleet.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (agentData?.status !== 'online') {
|
||||
return (
|
||||
<EuiEmptyPrompt
|
||||
icon={<OsqueryIcon />}
|
||||
title={<h2>Osquery is not available</h2>}
|
||||
titleSize="xs"
|
||||
body={
|
||||
<p>
|
||||
To run queries on this host, the Elastic Agent must be active. Check the status of this
|
||||
agent in Fleet.
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return <LiveQuery agentId={agentId} />;
|
||||
};
|
||||
|
||||
export const OsqueryAction = React.memo(OsqueryActionComponent);
|
||||
|
|
|
@ -22,10 +22,16 @@ export const getAgentDetailsRoute = (router: IRouter, osqueryContext: OsqueryApp
|
|||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
|
||||
const agent = await osqueryContext.service
|
||||
.getAgentService()
|
||||
// @ts-expect-error update types
|
||||
?.getAgent(esClient, request.params.id);
|
||||
let agent;
|
||||
|
||||
try {
|
||||
agent = await osqueryContext.service
|
||||
.getAgentService()
|
||||
// @ts-expect-error update types
|
||||
?.getAgent(esClient, request.params.id);
|
||||
} catch (err) {
|
||||
return response.notFound();
|
||||
}
|
||||
|
||||
return response.ok({ body: { item: agent } });
|
||||
}
|
||||
|
|
|
@ -18724,8 +18724,6 @@
|
|||
"xpack.osquery.scheduledQueryGroup.queriesTable.platformColumnTitle": "プラットフォーム",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.queryColumnTitle": "クエリ",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.versionColumnTitle": "最低Osqueryバージョン",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel": "Discoverで結果を表示",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel": "Lensで結果を表示",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.viewResultsColumnTitle": "結果を表示",
|
||||
"xpack.osquery.scheduledQueryGroup.queryFlyoutForm.cancelButtonLabel": "キャンセル",
|
||||
"xpack.osquery.scheduledQueryGroup.queryFlyoutForm.descriptionFieldLabel": "説明",
|
||||
|
|
|
@ -19138,8 +19138,6 @@
|
|||
"xpack.osquery.scheduledQueryGroup.queriesTable.platformColumnTitle": "平台",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.queryColumnTitle": "查询",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.versionColumnTitle": "最低 Osquery 版本",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.viewDiscoverResultsActionAriaLabel": "在 Discover 中查看结果",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.viewLensResultsActionAriaLabel": "在 Lens 中查看结果",
|
||||
"xpack.osquery.scheduledQueryGroup.queriesTable.viewResultsColumnTitle": "查看结果",
|
||||
"xpack.osquery.scheduledQueryGroup.queryFlyoutForm.cancelButtonLabel": "取消",
|
||||
"xpack.osquery.scheduledQueryGroup.queryFlyoutForm.descriptionFieldLabel": "描述",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue