mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[Osquery] RBAC (#106669)
This commit is contained in:
parent
5a92a7ef31
commit
9edcf9e71e
79 changed files with 1136 additions and 357 deletions
|
@ -43,6 +43,8 @@ export const REMOVED_TYPES: string[] = [
|
|||
'server',
|
||||
// https://github.com/elastic/kibana/issues/95617
|
||||
'tsvb-validation-telemetry',
|
||||
// replaced by osquery-manager-usage-metric
|
||||
'osquery-usage-metric',
|
||||
].sort();
|
||||
|
||||
// When migrating from the outdated index we use a read query which excludes
|
||||
|
|
|
@ -72,6 +72,7 @@ const previouslyRegisteredTypes = [
|
|||
'monitoring-telemetry',
|
||||
'osquery-saved-query',
|
||||
'osquery-usage-metric',
|
||||
'osquery-manager-usage-metric',
|
||||
'query',
|
||||
'sample-data-telemetry',
|
||||
'search',
|
||||
|
|
|
@ -44,7 +44,7 @@ export const getAgentUsage = async (
|
|||
error,
|
||||
offline,
|
||||
updating,
|
||||
} = await AgentService.getAgentStatusForAgentPolicy(soClient, esClient);
|
||||
} = await AgentService.getAgentStatusForAgentPolicy(esClient);
|
||||
return {
|
||||
total_enrolled: total,
|
||||
healthy: online,
|
||||
|
|
|
@ -76,7 +76,6 @@ export const getFleetServerUsage = async (
|
|||
}
|
||||
|
||||
const { total, inactive, online, error, updating, offline } = await getAgentStatusForAgentPolicy(
|
||||
soClient,
|
||||
esClient,
|
||||
undefined,
|
||||
Array.from(policyIds)
|
||||
|
|
|
@ -101,6 +101,7 @@ export const createMockAgentPolicyService = (): jest.Mocked<AgentPolicyServiceIn
|
|||
export const createMockAgentService = (): jest.Mocked<AgentService> => {
|
||||
return {
|
||||
getAgentStatusById: jest.fn(),
|
||||
getAgentStatusForAgentPolicy: jest.fn(),
|
||||
authenticateAgentWithAccessToken: jest.fn(),
|
||||
getAgent: jest.fn(),
|
||||
listAgents: jest.fn(),
|
||||
|
|
|
@ -72,6 +72,7 @@ import {
|
|||
} from './services';
|
||||
import {
|
||||
getAgentStatusById,
|
||||
getAgentStatusForAgentPolicy,
|
||||
authenticateAgentWithAccessToken,
|
||||
getAgentsByKuery,
|
||||
getAgentById,
|
||||
|
@ -309,6 +310,7 @@ export class FleetPlugin
|
|||
getAgent: getAgentById,
|
||||
listAgents: getAgentsByKuery,
|
||||
getAgentStatusById,
|
||||
getAgentStatusForAgentPolicy,
|
||||
authenticateAgentWithAccessToken,
|
||||
},
|
||||
agentPolicyService: {
|
||||
|
|
|
@ -202,13 +202,11 @@ export const getAgentStatusForAgentPolicyHandler: RequestHandler<
|
|||
undefined,
|
||||
TypeOf<typeof GetAgentStatusRequestSchema.query>
|
||||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
try {
|
||||
// TODO change path
|
||||
const results = await AgentService.getAgentStatusForAgentPolicy(
|
||||
soClient,
|
||||
esClient,
|
||||
request.query.policyId,
|
||||
request.query.kuery
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
|
||||
import type { ElasticsearchClient } from 'src/core/server';
|
||||
import pMap from 'p-map';
|
||||
|
||||
import { AGENT_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
|
@ -49,7 +49,6 @@ function joinKuerys(...kuerys: Array<string | undefined>) {
|
|||
}
|
||||
|
||||
export async function getAgentStatusForAgentPolicy(
|
||||
soClient: SavedObjectsClientContract,
|
||||
esClient: ElasticsearchClient,
|
||||
agentPolicyId?: string,
|
||||
filterKuery?: string
|
||||
|
|
|
@ -10,6 +10,8 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from 'kibana/ser
|
|||
|
||||
import type { AgentStatus, Agent } from '../types';
|
||||
|
||||
import type { GetAgentStatusResponse } from '../../common';
|
||||
|
||||
import type { getAgentById, getAgentsByKuery } from './agents';
|
||||
import type { agentPolicyService } from './agent_policy';
|
||||
import * as settingsService from './settings';
|
||||
|
@ -56,6 +58,14 @@ export interface AgentService {
|
|||
* Return the status by the Agent's id
|
||||
*/
|
||||
getAgentStatusById(esClient: ElasticsearchClient, agentId: string): Promise<AgentStatus>;
|
||||
/**
|
||||
* Return the status by the Agent's Policy id
|
||||
*/
|
||||
getAgentStatusForAgentPolicy(
|
||||
esClient: ElasticsearchClient,
|
||||
agentPolicyId?: string,
|
||||
filterKuery?: string
|
||||
): Promise<GetAgentStatusResponse['results']>;
|
||||
/**
|
||||
* List agents
|
||||
*/
|
||||
|
|
|
@ -9,8 +9,11 @@ import { PackagePolicy, PackagePolicyInput, PackagePolicyInputStream } from '../
|
|||
|
||||
export const savedQuerySavedObjectType = 'osquery-saved-query';
|
||||
export const packSavedObjectType = 'osquery-pack';
|
||||
export const usageMetricSavedObjectType = 'osquery-usage-metric';
|
||||
export type SavedObjectType = 'osquery-saved-query' | 'osquery-pack' | 'osquery-usage-metric';
|
||||
export const usageMetricSavedObjectType = 'osquery-manager-usage-metric';
|
||||
export type SavedObjectType =
|
||||
| 'osquery-saved-query'
|
||||
| 'osquery-pack'
|
||||
| 'osquery-manager-usage-metric';
|
||||
|
||||
/**
|
||||
* This makes any optional property the same as Required<T> would but also has the
|
||||
|
|
|
@ -15,6 +15,7 @@ import { AgentIdToName } from '../agents/agent_id_to_name';
|
|||
import { useActionResults } from './use_action_results';
|
||||
import { useAllResults } from '../results/use_all_results';
|
||||
import { Direction } from '../../common/search_strategy';
|
||||
import { useActionResultsPrivileges } from './use_action_privileges';
|
||||
|
||||
interface ActionResultsSummaryProps {
|
||||
actionId: string;
|
||||
|
@ -41,6 +42,7 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
|
|||
expirationDate,
|
||||
]);
|
||||
const [isLive, setIsLive] = useState(true);
|
||||
const { data: hasActionResultsPrivileges } = useActionResultsPrivileges();
|
||||
const {
|
||||
// @ts-expect-error update types
|
||||
data: { aggregations, edges },
|
||||
|
@ -52,6 +54,7 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
|
|||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
isLive,
|
||||
skip: !hasActionResultsPrivileges,
|
||||
});
|
||||
if (expired) {
|
||||
// @ts-expect-error update types
|
||||
|
@ -77,6 +80,7 @@ const ActionResultsSummaryComponent: React.FC<ActionResultsSummaryProps> = ({
|
|||
},
|
||||
],
|
||||
isLive,
|
||||
skip: !hasActionResultsPrivileges,
|
||||
});
|
||||
|
||||
const renderAgentIdColumn = useCallback((agentId) => <AgentIdToName agentId={agentId} />, []);
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { useQuery } from 'react-query';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
export const useActionResultsPrivileges = () => {
|
||||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
|
||||
return useQuery(
|
||||
['actionResultsPrivileges'],
|
||||
() => http.get('/internal/osquery/privileges_check'),
|
||||
{
|
||||
keepPreviousData: true,
|
||||
select: (response) => response?.has_all_requested ?? false,
|
||||
onSuccess: () => setErrorToast(),
|
||||
onError: (error: Error) =>
|
||||
setErrorToast(error, {
|
||||
title: i18n.translate('xpack.osquery.action_results_privileges.fetchError', {
|
||||
defaultMessage: 'Error while fetching action results privileges',
|
||||
}),
|
||||
}),
|
||||
}
|
||||
);
|
||||
};
|
|
@ -9,11 +9,7 @@ import { useQuery } from 'react-query';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
agentPolicyRouteService,
|
||||
GetAgentPoliciesResponse,
|
||||
GetAgentPoliciesResponseItem,
|
||||
} from '../../../fleet/common';
|
||||
import { GetAgentPoliciesResponse, GetAgentPoliciesResponseItem } from '../../../fleet/common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
export const useAgentPolicies = () => {
|
||||
|
@ -22,12 +18,7 @@ export const useAgentPolicies = () => {
|
|||
|
||||
return useQuery<GetAgentPoliciesResponse, unknown, GetAgentPoliciesResponseItem[]>(
|
||||
['agentPolicies'],
|
||||
() =>
|
||||
http.get(agentPolicyRouteService.getListPath(), {
|
||||
query: {
|
||||
perPage: 100,
|
||||
},
|
||||
}),
|
||||
() => http.get('/internal/osquery/fleet_wrapper/agent_policies/'),
|
||||
{
|
||||
initialData: { items: [], total: 0, page: 1, perPage: 100 },
|
||||
keepPreviousData: true,
|
||||
|
|
|
@ -9,7 +9,6 @@ import { useQuery } from 'react-query';
|
|||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { agentPolicyRouteService } from '../../../fleet/common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
interface UseAgentPolicy {
|
||||
|
@ -23,7 +22,7 @@ export const useAgentPolicy = ({ policyId, skip }: UseAgentPolicy) => {
|
|||
|
||||
return useQuery(
|
||||
['agentPolicy', { policyId }],
|
||||
() => http.get(agentPolicyRouteService.getInfoPath(policyId)),
|
||||
() => http.get(`/internal/osquery/fleet_wrapper/agent_policies/${policyId}`),
|
||||
{
|
||||
enabled: !skip,
|
||||
keepPreviousData: true,
|
||||
|
|
|
@ -65,9 +65,13 @@ const AgentsTableComponent: React.FC<AgentsTableProps> = ({ agentSelection, onCh
|
|||
osqueryPolicyData
|
||||
);
|
||||
const grouper = useMemo(() => new AgentGrouper(), []);
|
||||
const { agentsLoading, agents } = useAllAgents(osqueryPolicyData, debouncedSearchValue, {
|
||||
perPage,
|
||||
});
|
||||
const { isLoading: agentsLoading, data: agents } = useAllAgents(
|
||||
osqueryPolicyData,
|
||||
debouncedSearchValue,
|
||||
{
|
||||
perPage,
|
||||
}
|
||||
);
|
||||
|
||||
// option related
|
||||
const [options, setOptions] = useState<GroupOption[]>([]);
|
||||
|
@ -108,8 +112,8 @@ const AgentsTableComponent: React.FC<AgentsTableProps> = ({ agentSelection, onCh
|
|||
grouper.setTotalAgents(totalNumAgents);
|
||||
grouper.updateGroup(AGENT_GROUP_KEY.Platform, groups.platforms);
|
||||
grouper.updateGroup(AGENT_GROUP_KEY.Policy, groups.policies);
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents!);
|
||||
// @ts-expect-error update types
|
||||
grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents);
|
||||
const newOptions = grouper.generateOptions();
|
||||
setOptions(newOptions);
|
||||
}, [groups.platforms, groups.policies, totalNumAgents, groupsLoading, agents, grouper]);
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { GetOneAgentResponse, agentRouteService } from '../../../fleet/common';
|
||||
import { GetOneAgentResponse } from '../../../fleet/common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
|
@ -21,7 +21,7 @@ export const useAgentDetails = ({ agentId }: UseAgentDetails) => {
|
|||
const setErrorToast = useErrorToast();
|
||||
return useQuery<GetOneAgentResponse>(
|
||||
['agentDetails', agentId],
|
||||
() => http.get(agentRouteService.getInfoPath(agentId)),
|
||||
() => http.get(`/internal/osquery/fleet_wrapper/agents/${agentId}`),
|
||||
{
|
||||
enabled: agentId.length > 0,
|
||||
onSuccess: () => setErrorToast(),
|
||||
|
|
|
@ -9,7 +9,7 @@ import { mapKeys } from 'lodash';
|
|||
import { useQueries, UseQueryResult } from 'react-query';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { agentPolicyRouteService, GetOneAgentPolicyResponse } from '../../../fleet/common';
|
||||
import { GetOneAgentPolicyResponse } from '../../../fleet/common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
export const useAgentPolicies = (policyIds: string[] = []) => {
|
||||
|
@ -19,7 +19,7 @@ export const useAgentPolicies = (policyIds: string[] = []) => {
|
|||
const agentResponse = useQueries(
|
||||
policyIds.map((policyId) => ({
|
||||
queryKey: ['agentPolicy', policyId],
|
||||
queryFn: () => http.get(agentPolicyRouteService.getInfoPath(policyId)),
|
||||
queryFn: () => http.get(`/internal/osquery/fleet_wrapper/agent_policies/${policyId}`),
|
||||
enabled: policyIds.length > 0,
|
||||
onSuccess: () => setErrorToast(),
|
||||
onError: (error) =>
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { GetAgentStatusResponse, agentRouteService } from '../../../fleet/common';
|
||||
import { GetAgentStatusResponse } from '../../../fleet/common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
|
@ -25,7 +25,7 @@ export const useAgentStatus = ({ policyId, skip }: UseAgentStatus) => {
|
|||
['agentStatus', policyId],
|
||||
() =>
|
||||
http.get(
|
||||
agentRouteService.getStatusPath(),
|
||||
`/internal/osquery/fleet_wrapper/agent-status`,
|
||||
policyId
|
||||
? {
|
||||
query: {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { i18n } from '@kbn/i18n';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { GetAgentsResponse, agentRouteService } from '../../../fleet/common';
|
||||
import { GetAgentsResponse } from '../../../fleet/common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
|
@ -31,7 +31,8 @@ export const useAllAgents = (
|
|||
const { perPage } = opts;
|
||||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
const { isLoading: agentsLoading, data: agentData } = useQuery<GetAgentsResponse>(
|
||||
|
||||
return useQuery<GetAgentsResponse>(
|
||||
['agents', osqueryPolicies, searchValue, perPage],
|
||||
() => {
|
||||
let kuery = `${osqueryPolicies.map((p) => `policy_id:${p}`).join(' or ')}`;
|
||||
|
@ -40,7 +41,7 @@ export const useAllAgents = (
|
|||
kuery += ` and (local_metadata.host.hostname:*${searchValue}* or local_metadata.elastic.agent.id:*${searchValue}*)`;
|
||||
}
|
||||
|
||||
return http.get(agentRouteService.getListPath(), {
|
||||
return http.get(`/internal/osquery/fleet_wrapper/agents`, {
|
||||
query: {
|
||||
kuery,
|
||||
perPage,
|
||||
|
@ -48,6 +49,8 @@ export const useAllAgents = (
|
|||
});
|
||||
},
|
||||
{
|
||||
// @ts-expect-error update types
|
||||
select: (data) => data?.agents || [],
|
||||
enabled: !osqueryPoliciesLoading && osqueryPolicies.length > 0,
|
||||
onSuccess: () => setErrorToast(),
|
||||
onError: (error) =>
|
||||
|
@ -58,6 +61,4 @@ export const useAllAgents = (
|
|||
}),
|
||||
}
|
||||
);
|
||||
|
||||
return { agentsLoading, agents: agentData?.list };
|
||||
};
|
||||
|
|
|
@ -10,8 +10,6 @@ import { useQuery } from 'react-query';
|
|||
import { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { packagePolicyRouteService, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../fleet/common';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||
import { useErrorToast } from '../common/hooks/use_error_toast';
|
||||
|
||||
export const useOsqueryPolicies = () => {
|
||||
|
@ -20,12 +18,7 @@ export const useOsqueryPolicies = () => {
|
|||
|
||||
const { isLoading: osqueryPoliciesLoading, data: osqueryPolicies = [] } = useQuery(
|
||||
['osqueryPolicies'],
|
||||
() =>
|
||||
http.get(packagePolicyRouteService.getListPath(), {
|
||||
query: {
|
||||
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${OSQUERY_INTEGRATION_NAME}`,
|
||||
},
|
||||
}),
|
||||
() => http.get('/internal/osquery/fleet_wrapper/package_policies'),
|
||||
{
|
||||
select: (response) =>
|
||||
uniq<string>(response.items.map((p: { policy_id: string }) => p.policy_id)),
|
||||
|
|
|
@ -6,11 +6,8 @@
|
|||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { find } from 'lodash/fp';
|
||||
import { useQuery } from 'react-query';
|
||||
|
||||
import { GetPackagesResponse, epmRouteService } from '../../../../fleet/common';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
import { useKibana } from '../lib/kibana';
|
||||
import { useErrorToast } from './use_error_toast';
|
||||
|
||||
|
@ -18,23 +15,12 @@ export const useOsqueryIntegration = () => {
|
|||
const { http } = useKibana().services;
|
||||
const setErrorToast = useErrorToast();
|
||||
|
||||
return useQuery(
|
||||
'integrations',
|
||||
() =>
|
||||
http.get(epmRouteService.getListPath(), {
|
||||
query: {
|
||||
experimental: true,
|
||||
},
|
||||
}),
|
||||
{
|
||||
select: ({ response }: GetPackagesResponse) =>
|
||||
find(['name', OSQUERY_INTEGRATION_NAME], response),
|
||||
onError: (error: Error) =>
|
||||
setErrorToast(error, {
|
||||
title: i18n.translate('xpack.osquery.osquery_integration.fetchError', {
|
||||
defaultMessage: 'Error while fetching osquery integration',
|
||||
}),
|
||||
return useQuery('integration', () => http.get('/internal/osquery/status'), {
|
||||
onError: (error: Error) =>
|
||||
setErrorToast(error, {
|
||||
title: i18n.translate('xpack.osquery.osquery_integration.fetchError', {
|
||||
defaultMessage: 'Error while fetching osquery integration',
|
||||
}),
|
||||
}
|
||||
);
|
||||
}),
|
||||
});
|
||||
};
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiCallOut, EuiSpacer } from '@elastic/eui';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
||||
const DisabledCalloutComponent = () => (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
title={i18n.translate('xpack.osquery.fleetIntegration.saveIntegrationCalloutTitle', {
|
||||
defaultMessage: 'Save the integration to access the options below',
|
||||
})}
|
||||
iconType="save"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
|
||||
export const DisabledCallout = React.memo(DisabledCalloutComponent);
|
|
@ -5,16 +5,42 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { EuiLoadingContent } from '@elastic/eui';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { PackageCustomExtensionComponentProps } from '../../../fleet/public';
|
||||
import { NavigationButtons } from './navigation_buttons';
|
||||
import { DisabledCallout } from './disabled_callout';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
|
||||
/**
|
||||
* Exports Osquery-specific package policy instructions
|
||||
* for use in the Fleet app custom tab
|
||||
*/
|
||||
export const OsqueryManagedCustomButtonExtension = React.memo<PackageCustomExtensionComponentProps>(
|
||||
() => <NavigationButtons />
|
||||
() => {
|
||||
const [disabled, setDisabled] = React.useState<boolean | null>(null);
|
||||
const { http } = useKibana().services;
|
||||
|
||||
useEffect(() => {
|
||||
const fetchStatus = () => {
|
||||
http.get('/internal/osquery/status').then((response) => {
|
||||
setDisabled(response.install_status !== 'installed');
|
||||
});
|
||||
};
|
||||
fetchStatus();
|
||||
}, [http]);
|
||||
|
||||
if (disabled === null) {
|
||||
return <EuiLoadingContent lines={5} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{disabled ? <DisabledCallout /> : null}
|
||||
<NavigationButtons isDisabled={disabled} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
);
|
||||
OsqueryManagedCustomButtonExtension.displayName = 'OsqueryManagedCustomButtonExtension';
|
||||
|
|
|
@ -11,7 +11,6 @@ import React, { useEffect, useMemo, useState } from 'react';
|
|||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import { produce } from 'immer';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
agentRouteService,
|
||||
agentPolicyRouteService,
|
||||
|
@ -29,6 +28,7 @@ import {
|
|||
import { ScheduledQueryGroupQueriesTable } from '../scheduled_query_groups/scheduled_query_group_queries_table';
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { NavigationButtons } from './navigation_buttons';
|
||||
import { DisabledCallout } from './disabled_callout';
|
||||
import { OsqueryManagerPackagePolicy } from '../../common/types';
|
||||
|
||||
/**
|
||||
|
@ -163,22 +163,7 @@ export const OsqueryManagedPolicyCreateImportExtension = React.memo<
|
|||
|
||||
return (
|
||||
<>
|
||||
{!editMode ? (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiCallOut
|
||||
title={i18n.translate(
|
||||
'xpack.osquery.fleetIntegration.saveIntegrationCalloutTitle',
|
||||
{ defaultMessage: 'Save the integration to access the options below' }
|
||||
)}
|
||||
iconType="save"
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
) : null}
|
||||
{!editMode ? <DisabledCallout /> : null}
|
||||
{policyAgentsCount === 0 ? (
|
||||
<>
|
||||
<EuiFlexGroup>
|
||||
|
|
|
@ -47,6 +47,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
defaultValue,
|
||||
onSuccess,
|
||||
}) => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
const { http } = useKibana().services;
|
||||
const [showSavedQueryFlyout, setShowSavedQueryFlyout] = useState(false);
|
||||
const setErrorToast = useErrorToast();
|
||||
|
@ -175,7 +176,12 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
{!agentId && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty
|
||||
disabled={!agentSelected || !queryValueProvided || resultsStatus === 'disabled'}
|
||||
disabled={
|
||||
!permissions.writeSavedQueries ||
|
||||
!agentSelected ||
|
||||
!queryValueProvided ||
|
||||
resultsStatus === 'disabled'
|
||||
}
|
||||
onClick={handleShowSaveQueryFlout}
|
||||
>
|
||||
<FormattedMessage
|
||||
|
@ -199,6 +205,7 @@ const LiveQueryFormComponent: React.FC<LiveQueryFormProps> = ({
|
|||
[
|
||||
agentId,
|
||||
agentSelected,
|
||||
permissions.writeSavedQueries,
|
||||
handleShowSaveQueryFlout,
|
||||
queryComponentProps,
|
||||
queryValueProvided,
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { EuiFormRow, EuiSpacer } from '@elastic/eui';
|
||||
import { EuiCodeBlock, EuiFormRow, EuiSpacer } from '@elastic/eui';
|
||||
import React, { useCallback, useRef } from 'react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { OsquerySchemaLink } from '../../components/osquery_schema_link';
|
||||
import { FieldHook } from '../../shared_imports';
|
||||
|
@ -15,6 +16,11 @@ import {
|
|||
SavedQueriesDropdown,
|
||||
SavedQueriesDropdownRef,
|
||||
} from '../../saved_queries/saved_queries_dropdown';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
|
||||
const StyledEuiCodeBlock = styled(EuiCodeBlock)`
|
||||
min-height: 150px;
|
||||
`;
|
||||
|
||||
interface LiveQueryQueryFieldProps {
|
||||
disabled?: boolean;
|
||||
|
@ -22,6 +28,7 @@ interface LiveQueryQueryFieldProps {
|
|||
}
|
||||
|
||||
const LiveQueryQueryFieldComponent: React.FC<LiveQueryQueryFieldProps> = ({ disabled, field }) => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
const { value, setValue, errors } = field;
|
||||
const error = errors[0]?.message;
|
||||
const savedQueriesDropdownRef = useRef<SavedQueriesDropdownRef>(null);
|
||||
|
@ -46,12 +53,23 @@ const LiveQueryQueryFieldComponent: React.FC<LiveQueryQueryFieldProps> = ({ disa
|
|||
<>
|
||||
<SavedQueriesDropdown
|
||||
ref={savedQueriesDropdownRef}
|
||||
disabled={disabled}
|
||||
disabled={disabled || !permissions.runSavedQueries}
|
||||
onChange={handleSavedQueryChange}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<EuiFormRow fullWidth labelAppend={<OsquerySchemaLink />}>
|
||||
<OsqueryEditor defaultValue={value} disabled={disabled} onChange={handleEditorChange} />
|
||||
{!permissions.writeLiveQueries ? (
|
||||
<StyledEuiCodeBlock
|
||||
language="sql"
|
||||
fontSize="m"
|
||||
paddingSize="m"
|
||||
transparentBackground={!value.length}
|
||||
>
|
||||
{value}
|
||||
</StyledEuiCodeBlock>
|
||||
) : (
|
||||
<OsqueryEditor defaultValue={value} disabled={disabled} onChange={handleEditorChange} />
|
||||
)}
|
||||
</EuiFormRow>
|
||||
</>
|
||||
</EuiFormRow>
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { BehaviorSubject, Subject } from 'rxjs';
|
||||
import {
|
||||
AppMountParameters,
|
||||
CoreSetup,
|
||||
|
@ -13,9 +12,6 @@ import {
|
|||
PluginInitializerContext,
|
||||
CoreStart,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
AppStatus,
|
||||
AppNavLinkStatus,
|
||||
AppUpdater,
|
||||
} from '../../../../src/core/public';
|
||||
import { Storage } from '../../../../src/plugins/kibana_utils/public';
|
||||
import {
|
||||
|
@ -25,7 +21,6 @@ import {
|
|||
AppPluginStartDependencies,
|
||||
} from './types';
|
||||
import { OSQUERY_INTEGRATION_NAME, PLUGIN_NAME } from '../common';
|
||||
import { Installation } from '../../fleet/common';
|
||||
import {
|
||||
LazyOsqueryManagedPolicyCreateImportExtension,
|
||||
LazyOsqueryManagedPolicyEditExtension,
|
||||
|
@ -33,48 +28,7 @@ import {
|
|||
} from './fleet_integration';
|
||||
import { getLazyOsqueryAction } from './shared_components';
|
||||
|
||||
export function toggleOsqueryPlugin(
|
||||
updater$: Subject<AppUpdater>,
|
||||
http: CoreStart['http'],
|
||||
registerExtension?: StartPlugins['fleet']['registerExtension']
|
||||
) {
|
||||
if (http.anonymousPaths.isAnonymous(window.location.pathname)) {
|
||||
updater$.next(() => ({
|
||||
status: AppStatus.inaccessible,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
http
|
||||
.fetch<Installation | undefined>(`/internal/osquery/status`)
|
||||
.then((response) => {
|
||||
const installed = response?.install_status === 'installed';
|
||||
|
||||
if (installed && registerExtension) {
|
||||
registerExtension({
|
||||
package: OSQUERY_INTEGRATION_NAME,
|
||||
view: 'package-detail-custom',
|
||||
Component: LazyOsqueryManagedCustomButtonExtension,
|
||||
});
|
||||
}
|
||||
|
||||
updater$.next(() => ({
|
||||
navLinkStatus: installed ? AppNavLinkStatus.visible : AppNavLinkStatus.hidden,
|
||||
}));
|
||||
})
|
||||
.catch(() => {
|
||||
updater$.next(() => ({
|
||||
status: AppStatus.inaccessible,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginStart> {
|
||||
private readonly appUpdater$ = new BehaviorSubject<AppUpdater>(() => ({
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
}));
|
||||
private kibanaVersion: string;
|
||||
private storage = new Storage(localStorage);
|
||||
|
||||
|
@ -102,8 +56,6 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
id: 'osquery',
|
||||
title: PLUGIN_NAME,
|
||||
order: 9030,
|
||||
updater$: this.appUpdater$,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
async mount(params: AppMountParameters) {
|
||||
// Get start services as specified in kibana.json
|
||||
|
@ -141,8 +93,6 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
if (plugins.fleet) {
|
||||
const { registerExtension } = plugins.fleet;
|
||||
|
||||
toggleOsqueryPlugin(this.appUpdater$, core.http, registerExtension);
|
||||
|
||||
registerExtension({
|
||||
package: OSQUERY_INTEGRATION_NAME,
|
||||
view: 'package-policy-create',
|
||||
|
@ -154,11 +104,12 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
view: 'package-policy-edit',
|
||||
Component: LazyOsqueryManagedPolicyEditExtension,
|
||||
});
|
||||
} else {
|
||||
this.appUpdater$.next(() => ({
|
||||
status: AppStatus.inaccessible,
|
||||
navLinkStatus: AppNavLinkStatus.hidden,
|
||||
}));
|
||||
|
||||
registerExtension({
|
||||
package: OSQUERY_INTEGRATION_NAME,
|
||||
view: 'package-detail-custom',
|
||||
Component: LazyOsqueryManagedCustomButtonExtension,
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import { isEmpty, isEqual, keys, map } from 'lodash/fp';
|
||||
import {
|
||||
EuiCallOut,
|
||||
EuiCode,
|
||||
EuiDataGrid,
|
||||
EuiDataGridSorting,
|
||||
EuiDataGridProps,
|
||||
|
@ -31,6 +32,8 @@ import {
|
|||
ViewResultsInLensAction,
|
||||
ViewResultsActionButtonType,
|
||||
} from '../scheduled_query_groups/scheduled_query_group_queries_table';
|
||||
import { useActionResultsPrivileges } from '../action_results/use_action_privileges';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||
|
||||
const DataContext = createContext<ResultEdges>([]);
|
||||
|
||||
|
@ -49,6 +52,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
endDate,
|
||||
}) => {
|
||||
const [isLive, setIsLive] = useState(true);
|
||||
const { data: hasActionResultsPrivileges } = useActionResultsPrivileges();
|
||||
const {
|
||||
// @ts-expect-error update types
|
||||
data: { aggregations },
|
||||
|
@ -60,6 +64,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
direction: Direction.asc,
|
||||
sortField: '@timestamp',
|
||||
isLive,
|
||||
skip: !hasActionResultsPrivileges,
|
||||
});
|
||||
const expired = useMemo(() => (!endDate ? false : new Date(endDate) < new Date()), [endDate]);
|
||||
const { getUrlForApp } = useKibana().services.application;
|
||||
|
@ -104,6 +109,7 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
field: sortedColumn.id,
|
||||
direction: sortedColumn.direction as Direction,
|
||||
})),
|
||||
skip: !hasActionResultsPrivileges,
|
||||
});
|
||||
|
||||
const [visibleColumns, setVisibleColumns] = useState<string[]>([]);
|
||||
|
@ -237,6 +243,17 @@ const ResultsTableComponent: React.FC<ResultsTableComponentProps> = ({
|
|||
]
|
||||
);
|
||||
|
||||
if (!hasActionResultsPrivileges) {
|
||||
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>.
|
||||
</p>
|
||||
</EuiCallOut>
|
||||
);
|
||||
}
|
||||
|
||||
if (!isFetched) {
|
||||
return <EuiLoadingContent lines={5} />;
|
||||
}
|
||||
|
|
8
x-pack/plugins/osquery/public/routes/components/index.ts
Normal file
8
x-pack/plugins/osquery/public/routes/components/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
/*
|
||||
* 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 * from './missing_privileges';
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { EuiEmptyPrompt, EuiPanel, EuiSpacer } from '@elastic/eui';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Panel = styled(EuiPanel)`
|
||||
max-width: 500px;
|
||||
margin-right: auto;
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const MissingPrivilegesComponent = () => (
|
||||
<div>
|
||||
<EuiSpacer />
|
||||
<Panel>
|
||||
<EuiEmptyPrompt
|
||||
iconType="securityApp"
|
||||
title={
|
||||
<h2>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.permissionDeniedErrorTitle"
|
||||
defaultMessage="Permission denied"
|
||||
/>
|
||||
</h2>
|
||||
}
|
||||
body={
|
||||
<p>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.permissionDeniedErrorMessage"
|
||||
defaultMessage="You are not authorized to access this page."
|
||||
/>
|
||||
</p>
|
||||
}
|
||||
/>
|
||||
</Panel>
|
||||
<EuiSpacer />
|
||||
</div>
|
||||
);
|
||||
|
||||
export const MissingPrivileges = React.memo(MissingPrivilegesComponent);
|
|
@ -12,15 +12,26 @@ import { LiveQueriesPage } from './list';
|
|||
import { NewLiveQueryPage } from './new';
|
||||
import { LiveQueryDetailsPage } from './details';
|
||||
import { useBreadcrumbs } from '../../common/hooks/use_breadcrumbs';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { MissingPrivileges } from '../components';
|
||||
|
||||
const LiveQueriesComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
useBreadcrumbs('live_queries');
|
||||
const match = useRouteMatch();
|
||||
|
||||
if (!permissions.readLiveQueries) {
|
||||
return <MissingPrivileges />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/new`}>
|
||||
<NewLiveQueryPage />
|
||||
{permissions.runSavedQueries || permissions.writeLiveQueries ? (
|
||||
<NewLiveQueryPage />
|
||||
) : (
|
||||
<MissingPrivileges />
|
||||
)}
|
||||
</Route>
|
||||
<Route path={`${match.url}/:actionId`}>
|
||||
<LiveQueryDetailsPage />
|
||||
|
|
|
@ -9,13 +9,14 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { useKibana, useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { ActionsTable } from '../../../actions/actions_table';
|
||||
import { WithHeaderLayout } from '../../../components/layouts';
|
||||
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
|
||||
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
|
||||
|
||||
const LiveQueriesPageComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
useBreadcrumbs('live_queries');
|
||||
const newQueryLinkProps = useRouterNavigate('live_queries/new');
|
||||
|
||||
|
@ -40,14 +41,19 @@ const LiveQueriesPageComponent = () => {
|
|||
|
||||
const RightColumn = useMemo(
|
||||
() => (
|
||||
<EuiButton fill {...newQueryLinkProps} iconType="plusInCircle">
|
||||
<EuiButton
|
||||
fill
|
||||
{...newQueryLinkProps}
|
||||
iconType="plusInCircle"
|
||||
isDisabled={!(permissions.writeLiveQueries || permissions.runSavedQueries)}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.liveQueriesHistory.newLiveQueryButtonLabel"
|
||||
defaultMessage="New live query"
|
||||
/>
|
||||
</EuiButton>
|
||||
),
|
||||
[newQueryLinkProps]
|
||||
[permissions.writeLiveQueries, permissions.runSavedQueries, newQueryLinkProps]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -24,11 +24,13 @@ import { useSavedQueryForm } from '../../../saved_queries/form/use_saved_query_f
|
|||
interface EditSavedQueryFormProps {
|
||||
defaultValue?: unknown;
|
||||
handleSubmit: () => Promise<void>;
|
||||
viewMode?: boolean;
|
||||
}
|
||||
|
||||
const EditSavedQueryFormComponent: React.FC<EditSavedQueryFormProps> = ({
|
||||
defaultValue,
|
||||
handleSubmit,
|
||||
viewMode,
|
||||
}) => {
|
||||
const savedQueryListProps = useRouterNavigate('saved_queries');
|
||||
|
||||
|
@ -39,41 +41,45 @@ const EditSavedQueryFormComponent: React.FC<EditSavedQueryFormProps> = ({
|
|||
|
||||
return (
|
||||
<Form form={form}>
|
||||
<SavedQueryForm />
|
||||
<EuiBottomBar>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<SavedQueryForm viewMode={viewMode} />
|
||||
{!viewMode && (
|
||||
<>
|
||||
<EuiBottomBar>
|
||||
<EuiFlexGroup justifyContent="flexEnd">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" {...savedQueryListProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.form.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
// isLoading={isLoading}
|
||||
color="primary"
|
||||
fill
|
||||
size="m"
|
||||
iconType="save"
|
||||
onClick={form.submit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.form.updateQueryButtonLabel"
|
||||
defaultMessage="Update query"
|
||||
/>
|
||||
</EuiButton>
|
||||
<EuiFlexGroup gutterSize="m">
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButtonEmpty color="ghost" {...savedQueryListProps}>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.form.cancelButtonLabel"
|
||||
defaultMessage="Cancel"
|
||||
/>
|
||||
</EuiButtonEmpty>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false}>
|
||||
<EuiButton
|
||||
// isLoading={isLoading}
|
||||
color="primary"
|
||||
fill
|
||||
size="m"
|
||||
iconType="save"
|
||||
onClick={form.submit}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.form.updateQueryButtonLabel"
|
||||
defaultMessage="Update query"
|
||||
/>
|
||||
</EuiButton>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</EuiBottomBar>
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiSpacer size="xxl" />
|
||||
</EuiBottomBar>
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiSpacer size="xxl" />
|
||||
<EuiSpacer size="xxl" />
|
||||
</>
|
||||
)}
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ import React, { useCallback, useMemo, useState } from 'react';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { useKibana, useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { WithHeaderLayout } from '../../../components/layouts';
|
||||
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
|
||||
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
|
||||
|
@ -25,6 +25,8 @@ import { EditSavedQueryForm } from './form';
|
|||
import { useDeleteSavedQuery, useUpdateSavedQuery, useSavedQuery } from '../../../saved_queries';
|
||||
|
||||
const EditSavedQueryPageComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
|
||||
const [isDeleteModalVisible, setIsDeleteModalVisible] = useState(false);
|
||||
const { savedQueryId } = useParams<{ savedQueryId: string }>();
|
||||
const savedQueryListProps = useRouterNavigate('saved_queries');
|
||||
|
@ -35,6 +37,8 @@ const EditSavedQueryPageComponent = () => {
|
|||
|
||||
useBreadcrumbs('saved_query_edit', { savedQueryName: savedQueryDetails?.attributes?.id ?? '' });
|
||||
|
||||
const viewMode = useMemo(() => !permissions.writeSavedQueries, [permissions.writeSavedQueries]);
|
||||
|
||||
const handleCloseDeleteConfirmationModal = useCallback(() => {
|
||||
setIsDeleteModalVisible(false);
|
||||
}, []);
|
||||
|
@ -63,21 +67,32 @@ const EditSavedQueryPageComponent = () => {
|
|||
<EuiFlexItem>
|
||||
<BetaBadgeRowWrapper>
|
||||
<h1>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.pageTitle"
|
||||
defaultMessage='Edit "{savedQueryId}"'
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{
|
||||
savedQueryId: savedQueryDetails?.attributes?.id ?? '',
|
||||
}}
|
||||
/>
|
||||
{viewMode ? (
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.viewSavedQuery.pageTitle"
|
||||
defaultMessage='"{savedQueryId}" details'
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{
|
||||
savedQueryId: savedQueryDetails?.attributes?.id ?? '',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.editSavedQuery.pageTitle"
|
||||
defaultMessage='Edit "{savedQueryId}"'
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
values={{
|
||||
savedQueryId: savedQueryDetails?.attributes?.id ?? '',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</h1>
|
||||
<BetaBadge />
|
||||
</BetaBadgeRowWrapper>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[savedQueryDetails?.attributes?.id, savedQueryListProps]
|
||||
[savedQueryDetails?.attributes?.id, savedQueryListProps, viewMode]
|
||||
);
|
||||
|
||||
const RightColumn = useMemo(
|
||||
|
@ -95,12 +110,17 @@ const EditSavedQueryPageComponent = () => {
|
|||
if (isLoading) return null;
|
||||
|
||||
return (
|
||||
<WithHeaderLayout leftColumn={LeftColumn} rightColumn={RightColumn} rightColumnGrow={false}>
|
||||
<WithHeaderLayout
|
||||
leftColumn={LeftColumn}
|
||||
rightColumn={!viewMode ? RightColumn : undefined}
|
||||
rightColumnGrow={false}
|
||||
>
|
||||
{!isLoading && !isEmpty(savedQueryDetails) && (
|
||||
<EditSavedQueryForm
|
||||
defaultValue={savedQueryDetails?.attributes}
|
||||
// @ts-expect-error update types
|
||||
handleSubmit={updateSavedQueryMutation.mutateAsync}
|
||||
viewMode={viewMode}
|
||||
/>
|
||||
)}
|
||||
{isDeleteModalVisible ? (
|
||||
|
|
|
@ -12,15 +12,22 @@ import { QueriesPage } from './list';
|
|||
import { NewSavedQueryPage } from './new';
|
||||
import { EditSavedQueryPage } from './edit';
|
||||
import { useBreadcrumbs } from '../../common/hooks/use_breadcrumbs';
|
||||
import { MissingPrivileges } from '../components';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
|
||||
const SavedQueriesComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
useBreadcrumbs('saved_queries');
|
||||
const match = useRouteMatch();
|
||||
|
||||
if (!permissions.readSavedQueries) {
|
||||
return <MissingPrivileges />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/new`}>
|
||||
<NewSavedQueryPage />
|
||||
{permissions.writeSavedQueries ? <NewSavedQueryPage /> : <MissingPrivileges />}
|
||||
</Route>
|
||||
<Route path={`${match.url}/:savedQueryId`}>
|
||||
<EditSavedQueryPage />
|
||||
|
|
|
@ -21,16 +21,63 @@ import { useHistory } from 'react-router-dom';
|
|||
import { SavedObject } from 'kibana/public';
|
||||
import { WithHeaderLayout } from '../../../components/layouts';
|
||||
import { useBreadcrumbs } from '../../../common/hooks/use_breadcrumbs';
|
||||
import { useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { useKibana, useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
|
||||
import { useSavedQueries } from '../../../saved_queries/use_saved_queries';
|
||||
|
||||
interface EditButtonProps {
|
||||
interface PlayButtonProps {
|
||||
disabled: boolean;
|
||||
savedQueryId: string;
|
||||
savedQueryName: string;
|
||||
}
|
||||
|
||||
const EditButtonComponent: React.FC<EditButtonProps> = ({ savedQueryId, savedQueryName }) => {
|
||||
const PlayButtonComponent: React.FC<PlayButtonProps> = ({
|
||||
disabled = false,
|
||||
savedQueryId,
|
||||
savedQueryName,
|
||||
}) => {
|
||||
const { push } = useHistory();
|
||||
|
||||
// TODO: Fix href
|
||||
const handlePlayClick = useCallback(
|
||||
() =>
|
||||
push('/live_queries/new', {
|
||||
form: {
|
||||
savedQueryId,
|
||||
},
|
||||
}),
|
||||
[push, savedQueryId]
|
||||
);
|
||||
|
||||
return (
|
||||
<EuiButtonIcon
|
||||
color="primary"
|
||||
iconType="play"
|
||||
isDisabled={disabled}
|
||||
onClick={handlePlayClick}
|
||||
aria-label={i18n.translate('xpack.osquery.savedQueryList.queriesTable.runActionAriaLabel', {
|
||||
defaultMessage: 'Run {savedQueryName}',
|
||||
values: {
|
||||
savedQueryName,
|
||||
},
|
||||
})}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
const PlayButton = React.memo(PlayButtonComponent);
|
||||
|
||||
interface EditButtonProps {
|
||||
disabled?: boolean;
|
||||
savedQueryId: string;
|
||||
savedQueryName: string;
|
||||
}
|
||||
|
||||
const EditButtonComponent: React.FC<EditButtonProps> = ({
|
||||
disabled = false,
|
||||
savedQueryId,
|
||||
savedQueryName,
|
||||
}) => {
|
||||
const buttonProps = useRouterNavigate(`saved_queries/${savedQueryId}`);
|
||||
|
||||
return (
|
||||
|
@ -38,6 +85,7 @@ const EditButtonComponent: React.FC<EditButtonProps> = ({ savedQueryId, savedQue
|
|||
color="primary"
|
||||
{...buttonProps}
|
||||
iconType="pencil"
|
||||
isDisabled={disabled}
|
||||
aria-label={i18n.translate('xpack.osquery.savedQueryList.queriesTable.editActionAriaLabel', {
|
||||
defaultMessage: 'Edit {savedQueryName}',
|
||||
values: {
|
||||
|
@ -51,8 +99,9 @@ const EditButtonComponent: React.FC<EditButtonProps> = ({ savedQueryId, savedQue
|
|||
const EditButton = React.memo(EditButtonComponent);
|
||||
|
||||
const SavedQueriesPageComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
|
||||
useBreadcrumbs('saved_queries');
|
||||
const { push } = useHistory();
|
||||
const newQueryLinkProps = useRouterNavigate('saved_queries/new');
|
||||
const [pageIndex, setPageIndex] = useState(0);
|
||||
const [pageSize, setPageSize] = useState(20);
|
||||
|
@ -61,16 +110,6 @@ const SavedQueriesPageComponent = () => {
|
|||
|
||||
const { data } = useSavedQueries({ isLive: true });
|
||||
|
||||
const handlePlayClick = useCallback(
|
||||
(item) =>
|
||||
push('/live_queries/new', {
|
||||
form: {
|
||||
savedQueryId: item.id,
|
||||
},
|
||||
}),
|
||||
[push]
|
||||
);
|
||||
|
||||
const renderEditAction = useCallback(
|
||||
(item: SavedObject<{ name: string }>) => (
|
||||
<EditButton savedQueryId={item.id} savedQueryName={item.attributes.name} />
|
||||
|
@ -78,6 +117,17 @@ const SavedQueriesPageComponent = () => {
|
|||
[]
|
||||
);
|
||||
|
||||
const renderPlayAction = useCallback(
|
||||
(item: SavedObject<{ name: string }>) => (
|
||||
<PlayButton
|
||||
savedQueryId={item.id}
|
||||
savedQueryName={item.attributes.name}
|
||||
disabled={!(permissions.runSavedQueries || permissions.writeLiveQueries)}
|
||||
/>
|
||||
),
|
||||
[permissions.runSavedQueries, permissions.writeLiveQueries]
|
||||
);
|
||||
|
||||
const renderUpdatedAt = useCallback((updatedAt, item) => {
|
||||
if (!updatedAt) return '-';
|
||||
|
||||
|
@ -128,17 +178,10 @@ const SavedQueriesPageComponent = () => {
|
|||
name: i18n.translate('xpack.osquery.savedQueries.table.actionsColumnTitle', {
|
||||
defaultMessage: 'Actions',
|
||||
}),
|
||||
actions: [
|
||||
{
|
||||
type: 'icon',
|
||||
icon: 'play',
|
||||
onClick: handlePlayClick,
|
||||
},
|
||||
{ render: renderEditAction },
|
||||
],
|
||||
actions: [{ render: renderPlayAction }, { render: renderEditAction }],
|
||||
},
|
||||
],
|
||||
[handlePlayClick, renderEditAction, renderUpdatedAt]
|
||||
[renderEditAction, renderPlayAction, renderUpdatedAt]
|
||||
);
|
||||
|
||||
const onTableChange = useCallback(({ page = {}, sort = {} }) => {
|
||||
|
@ -189,14 +232,19 @@ const SavedQueriesPageComponent = () => {
|
|||
|
||||
const RightColumn = useMemo(
|
||||
() => (
|
||||
<EuiButton fill {...newQueryLinkProps} iconType="plusInCircle">
|
||||
<EuiButton
|
||||
fill
|
||||
{...newQueryLinkProps}
|
||||
iconType="plusInCircle"
|
||||
isDisabled={!permissions.writeSavedQueries}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.savedQueryList.addSavedQueryButtonLabel"
|
||||
defaultMessage="Add saved query"
|
||||
/>
|
||||
</EuiButton>
|
||||
),
|
||||
[newQueryLinkProps]
|
||||
[permissions.writeSavedQueries, newQueryLinkProps]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -21,7 +21,7 @@ import React, { useMemo } from 'react';
|
|||
import { useParams } from 'react-router-dom';
|
||||
import styled from 'styled-components';
|
||||
|
||||
import { useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { useKibana, useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { WithHeaderLayout } from '../../../components/layouts';
|
||||
import { useScheduledQueryGroup } from '../../../scheduled_query_groups/use_scheduled_query_group';
|
||||
import { ScheduledQueryGroupQueriesTable } from '../../../scheduled_query_groups/scheduled_query_group_queries_table';
|
||||
|
@ -36,6 +36,7 @@ const Divider = styled.div`
|
|||
`;
|
||||
|
||||
const ScheduledQueryGroupDetailsPageComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
const { scheduledQueryGroupId } = useParams<{ scheduledQueryGroupId: string }>();
|
||||
const scheduledQueryGroupsListProps = useRouterNavigate('scheduled_query_groups');
|
||||
const editQueryLinkProps = useRouterNavigate(
|
||||
|
@ -111,7 +112,12 @@ const ScheduledQueryGroupDetailsPageComponent = () => {
|
|||
<Divider />
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem grow={false} key="edit_button">
|
||||
<EuiButton fill {...editQueryLinkProps} iconType="pencil">
|
||||
<EuiButton
|
||||
fill
|
||||
{...editQueryLinkProps}
|
||||
iconType="pencil"
|
||||
isDisabled={!permissions.writePacks}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryDetailsPage.editQueryButtonLabel"
|
||||
defaultMessage="Edit"
|
||||
|
@ -120,7 +126,7 @@ const ScheduledQueryGroupDetailsPageComponent = () => {
|
|||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
),
|
||||
[data?.policy_id, editQueryLinkProps]
|
||||
[data?.policy_id, editQueryLinkProps, permissions]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -13,18 +13,25 @@ import { AddScheduledQueryGroupPage } from './add';
|
|||
import { EditScheduledQueryGroupPage } from './edit';
|
||||
import { ScheduledQueryGroupDetailsPage } from './details';
|
||||
import { useBreadcrumbs } from '../../common/hooks/use_breadcrumbs';
|
||||
import { useKibana } from '../../common/lib/kibana';
|
||||
import { MissingPrivileges } from '../components';
|
||||
|
||||
const ScheduledQueryGroupsComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
useBreadcrumbs('scheduled_query_groups');
|
||||
const match = useRouteMatch();
|
||||
|
||||
if (!permissions.readPacks) {
|
||||
return <MissingPrivileges />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Switch>
|
||||
<Route path={`${match.url}/add`}>
|
||||
<AddScheduledQueryGroupPage />
|
||||
{permissions.writePacks ? <AddScheduledQueryGroupPage /> : <MissingPrivileges />}
|
||||
</Route>
|
||||
<Route path={`${match.url}/:scheduledQueryGroupId/edit`}>
|
||||
<EditScheduledQueryGroupPage />
|
||||
{permissions.writePacks ? <EditScheduledQueryGroupPage /> : <MissingPrivileges />}
|
||||
</Route>
|
||||
<Route path={`${match.url}/:scheduledQueryGroupId`}>
|
||||
<ScheduledQueryGroupDetailsPage />
|
||||
|
|
|
@ -9,12 +9,13 @@ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
|
|||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { useKibana, useRouterNavigate } from '../../../common/lib/kibana';
|
||||
import { WithHeaderLayout } from '../../../components/layouts';
|
||||
import { ScheduledQueryGroupsTable } from '../../../scheduled_query_groups/scheduled_query_groups_table';
|
||||
import { BetaBadge, BetaBadgeRowWrapper } from '../../../components/beta_badge';
|
||||
|
||||
const ScheduledQueryGroupsPageComponent = () => {
|
||||
const permissions = useKibana().services.application.capabilities.osquery;
|
||||
const newQueryLinkProps = useRouterNavigate('scheduled_query_groups/add');
|
||||
|
||||
const LeftColumn = useMemo(
|
||||
|
@ -38,14 +39,19 @@ const ScheduledQueryGroupsPageComponent = () => {
|
|||
|
||||
const RightColumn = useMemo(
|
||||
() => (
|
||||
<EuiButton fill {...newQueryLinkProps} iconType="plusInCircle">
|
||||
<EuiButton
|
||||
fill
|
||||
{...newQueryLinkProps}
|
||||
iconType="plusInCircle"
|
||||
isDisabled={!permissions.writePacks}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.scheduledQueryList.addScheduledQueryButtonLabel"
|
||||
defaultMessage="Add scheduled query group"
|
||||
/>
|
||||
</EuiButton>
|
||||
),
|
||||
[newQueryLinkProps]
|
||||
[newQueryLinkProps, permissions.writePacks]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiTitle, EuiText } from '@elastic/eui';
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import { FormattedMessage } from '@kbn/i18n/react';
|
||||
|
||||
|
@ -17,64 +17,78 @@ import { CodeEditorField } from './code_editor_field';
|
|||
|
||||
export const CommonUseField = getUseField({ component: Field });
|
||||
|
||||
const SavedQueryFormComponent = () => (
|
||||
<>
|
||||
<CommonUseField path="id" />
|
||||
<EuiSpacer />
|
||||
<CommonUseField path="description" />
|
||||
<EuiSpacer />
|
||||
<UseField path="query" component={CodeEditorField} />
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
interface SavedQueryFormProps {
|
||||
viewMode?: boolean;
|
||||
}
|
||||
|
||||
const SavedQueryFormComponent: React.FC<SavedQueryFormProps> = ({ viewMode }) => {
|
||||
const euiFieldProps = useMemo(
|
||||
() => ({
|
||||
isDisabled: !!viewMode,
|
||||
}),
|
||||
[viewMode]
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<CommonUseField path="id" euiFieldProps={euiFieldProps} />
|
||||
<EuiSpacer />
|
||||
<CommonUseField path="description" euiFieldProps={euiFieldProps} />
|
||||
<EuiSpacer />
|
||||
<UseField path="query" component={CodeEditorField} euiFieldProps={euiFieldProps} />
|
||||
<EuiSpacer size="xl" />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiTitle size="xs">
|
||||
<h5>
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.savedQueries.form.scheduledQueryGroupConfigSection.title"
|
||||
defaultMessage="Scheduled query group configuration"
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.savedQueries.form.scheduledQueryGroupConfigSection.title"
|
||||
defaultMessage="Scheduled query group configuration"
|
||||
id="xpack.osquery.savedQueries.form.scheduledQueryGroupConfigSection.description"
|
||||
defaultMessage="The options listed below are optional and are only applied when the query is assigned to a scheduled query group."
|
||||
/>
|
||||
</h5>
|
||||
</EuiTitle>
|
||||
<EuiText color="subdued">
|
||||
<FormattedMessage
|
||||
id="xpack.osquery.savedQueries.form.scheduledQueryGroupConfigSection.description"
|
||||
defaultMessage="The options listed below are optional and are only applied when the query is assigned to a scheduled query group."
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<CommonUseField
|
||||
path="interval"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
euiFieldProps={{ append: 's', ...euiFieldProps }}
|
||||
/>
|
||||
</EuiText>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<CommonUseField
|
||||
path="interval"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
euiFieldProps={{ append: 's' }}
|
||||
/>
|
||||
<EuiSpacer />
|
||||
<CommonUseField
|
||||
path="version"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
euiFieldProps={{
|
||||
noSuggestions: false,
|
||||
singleSelection: { asPlainText: true },
|
||||
placeholder: i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel',
|
||||
{
|
||||
defaultMessage: 'ALL',
|
||||
}
|
||||
),
|
||||
options: ALL_OSQUERY_VERSIONS_OPTIONS,
|
||||
onCreateOption: undefined,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<CommonUseField path="platform" component={PlatformCheckBoxGroupField} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
<EuiSpacer />
|
||||
<CommonUseField
|
||||
path="version"
|
||||
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
|
||||
euiFieldProps={{
|
||||
noSuggestions: false,
|
||||
singleSelection: { asPlainText: true },
|
||||
placeholder: i18n.translate(
|
||||
'xpack.osquery.scheduledQueryGroup.queriesTable.osqueryVersionAllLabel',
|
||||
{
|
||||
defaultMessage: 'ALL',
|
||||
}
|
||||
),
|
||||
options: ALL_OSQUERY_VERSIONS_OPTIONS,
|
||||
onCreateOption: undefined,
|
||||
...euiFieldProps,
|
||||
}}
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
<EuiFlexItem>
|
||||
<CommonUseField path="platform" component={PlatformCheckBoxGroupField} />
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
<EuiSpacer />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export const SavedQueryForm = React.memo(SavedQueryFormComponent);
|
||||
|
|
|
@ -28,12 +28,16 @@ const StyledEuiLoadingSpinner = styled(EuiLoadingSpinner)`
|
|||
`;
|
||||
|
||||
interface ActiveStateSwitchProps {
|
||||
disabled?: boolean;
|
||||
item: PackagePolicy;
|
||||
}
|
||||
|
||||
const ActiveStateSwitchComponent: React.FC<ActiveStateSwitchProps> = ({ item }) => {
|
||||
const queryClient = useQueryClient();
|
||||
const {
|
||||
application: {
|
||||
capabilities: { osquery: permissions },
|
||||
},
|
||||
http,
|
||||
notifications: { toasts },
|
||||
} = useKibana().services;
|
||||
|
@ -126,7 +130,7 @@ const ActiveStateSwitchComponent: React.FC<ActiveStateSwitchProps> = ({ item })
|
|||
{isLoading && <StyledEuiLoadingSpinner />}
|
||||
<EuiSwitch
|
||||
checked={item.enabled}
|
||||
disabled={isLoading}
|
||||
disabled={!permissions.writePacks || isLoading}
|
||||
showLabel={false}
|
||||
label=""
|
||||
onChange={handleToggleActiveClick}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import { GetOnePackagePolicyResponse, packagePolicyRouteService } from '../../../fleet/common';
|
||||
import { GetOnePackagePolicyResponse } from '../../../fleet/common';
|
||||
import { OsqueryManagerPackagePolicy } from '../../common/types';
|
||||
|
||||
interface UseScheduledQueryGroup {
|
||||
|
@ -28,7 +28,7 @@ export const useScheduledQueryGroup = ({
|
|||
OsqueryManagerPackagePolicy
|
||||
>(
|
||||
['scheduledQueryGroup', { scheduledQueryGroupId }],
|
||||
() => http.get(packagePolicyRouteService.getInfoPath(scheduledQueryGroupId)),
|
||||
() => http.get(`/internal/osquery/scheduled_query_group/${scheduledQueryGroupId}`),
|
||||
{
|
||||
keepPreviousData: true,
|
||||
enabled: !skip || !scheduledQueryGroupId,
|
||||
|
|
|
@ -9,12 +9,7 @@ import { produce } from 'immer';
|
|||
import { useQuery } from 'react-query';
|
||||
|
||||
import { useKibana } from '../common/lib/kibana';
|
||||
import {
|
||||
ListResult,
|
||||
PackagePolicy,
|
||||
packagePolicyRouteService,
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
} from '../../../fleet/common';
|
||||
import { ListResult, PackagePolicy, PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../fleet/common';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../common';
|
||||
|
||||
export const useScheduledQueryGroups = () => {
|
||||
|
@ -23,7 +18,7 @@ export const useScheduledQueryGroups = () => {
|
|||
return useQuery<ListResult<PackagePolicy>>(
|
||||
['scheduledQueries'],
|
||||
() =>
|
||||
http.get(packagePolicyRouteService.getListPath(), {
|
||||
http.get('/internal/osquery/scheduled_query_group', {
|
||||
query: {
|
||||
page: 1,
|
||||
perPage: 10000,
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { Logger, LoggerFactory } from 'src/core/server';
|
||||
import { CoreSetup, Logger, LoggerFactory } from '../../../../../src/core/server';
|
||||
import { SecurityPluginStart } from '../../../security/server';
|
||||
import {
|
||||
AgentService,
|
||||
|
@ -71,6 +71,7 @@ export interface OsqueryAppContext {
|
|||
logFactory: LoggerFactory;
|
||||
config(): ConfigType;
|
||||
security: SecurityPluginStart;
|
||||
getStartServices: CoreSetup['getStartServices'];
|
||||
/**
|
||||
* Object readiness is tied to plugin start method
|
||||
*/
|
||||
|
|
|
@ -5,14 +5,20 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
import {
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
} from '../../fleet/common';
|
||||
import {
|
||||
PluginInitializerContext,
|
||||
CoreSetup,
|
||||
CoreStart,
|
||||
Plugin,
|
||||
Logger,
|
||||
DEFAULT_APP_CATEGORIES,
|
||||
} from '../../../../src/core/server';
|
||||
|
||||
import { createConfig } from './create_config';
|
||||
import { OsqueryPluginSetup, OsqueryPluginStart, SetupPlugins, StartPlugins } from './types';
|
||||
import { defineRoutes } from './routes';
|
||||
|
@ -21,6 +27,169 @@ import { initSavedObjects } from './saved_objects';
|
|||
import { initUsageCollectors } from './usage';
|
||||
import { OsqueryAppContext, OsqueryAppContextService } from './lib/osquery_app_context_services';
|
||||
import { ConfigType } from './config';
|
||||
import { packSavedObjectType, savedQuerySavedObjectType } from '../common/types';
|
||||
import { PLUGIN_ID } from '../common';
|
||||
|
||||
const registerFeatures = (features: SetupPlugins['features']) => {
|
||||
features.registerKibanaFeature({
|
||||
id: PLUGIN_ID,
|
||||
name: i18n.translate('xpack.osquery.features.osqueryFeatureName', {
|
||||
defaultMessage: 'Osquery',
|
||||
}),
|
||||
category: DEFAULT_APP_CATEGORIES.management,
|
||||
app: [PLUGIN_ID, 'kibana'],
|
||||
catalogue: [PLUGIN_ID],
|
||||
order: 2300,
|
||||
excludeFromBasePrivileges: true,
|
||||
privileges: {
|
||||
all: {
|
||||
api: [`${PLUGIN_ID}-read`, `${PLUGIN_ID}-write`],
|
||||
app: [PLUGIN_ID, 'kibana'],
|
||||
catalogue: [PLUGIN_ID],
|
||||
savedObject: {
|
||||
all: [PACKAGE_POLICY_SAVED_OBJECT_TYPE],
|
||||
read: [PACKAGES_SAVED_OBJECT_TYPE, AGENT_POLICY_SAVED_OBJECT_TYPE],
|
||||
},
|
||||
ui: ['write'],
|
||||
},
|
||||
read: {
|
||||
api: [`${PLUGIN_ID}-read`],
|
||||
app: [PLUGIN_ID, 'kibana'],
|
||||
catalogue: [PLUGIN_ID],
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [
|
||||
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
|
||||
PACKAGES_SAVED_OBJECT_TYPE,
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
],
|
||||
},
|
||||
ui: ['read'],
|
||||
},
|
||||
},
|
||||
subFeatures: [
|
||||
{
|
||||
name: i18n.translate('xpack.osquery.features.liveQueriesSubFeatureName', {
|
||||
defaultMessage: 'Live queries',
|
||||
}),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
api: [`${PLUGIN_ID}-writeLiveQueries`, `${PLUGIN_ID}-readLiveQueries`],
|
||||
id: 'live_queries_all',
|
||||
includeIn: 'all',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeLiveQueries', 'readLiveQueries'],
|
||||
},
|
||||
{
|
||||
api: [`${PLUGIN_ID}-readLiveQueries`],
|
||||
id: 'live_queries_read',
|
||||
includeIn: 'read',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['readLiveQueries'],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
groupType: 'independent',
|
||||
privileges: [
|
||||
{
|
||||
api: [`${PLUGIN_ID}-runSavedQueries`],
|
||||
id: 'run_saved_queries',
|
||||
name: i18n.translate('xpack.osquery.features.runSavedQueriesPrivilegeName', {
|
||||
defaultMessage: 'Run Saved queries',
|
||||
}),
|
||||
includeIn: 'all',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [],
|
||||
},
|
||||
ui: ['runSavedQueries'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: i18n.translate('xpack.osquery.features.savedQueriesSubFeatureName', {
|
||||
defaultMessage: 'Saved queries',
|
||||
}),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
id: 'saved_queries_all',
|
||||
includeIn: 'all',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [savedQuerySavedObjectType],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writeSavedQueries', 'readSavedQueries'],
|
||||
},
|
||||
{
|
||||
id: 'saved_queries_read',
|
||||
includeIn: 'read',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [savedQuerySavedObjectType],
|
||||
},
|
||||
ui: ['readSavedQueries'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
// TODO: Rename it to "Packs" as part of https://github.com/elastic/kibana/pull/107345
|
||||
name: i18n.translate('xpack.osquery.features.scheduledQueryGroupsSubFeatureName', {
|
||||
defaultMessage: 'Scheduled query groups',
|
||||
}),
|
||||
privilegeGroups: [
|
||||
{
|
||||
groupType: 'mutually_exclusive',
|
||||
privileges: [
|
||||
{
|
||||
api: [`${PLUGIN_ID}-writePacks`],
|
||||
id: 'packs_all',
|
||||
includeIn: 'all',
|
||||
name: 'All',
|
||||
savedObject: {
|
||||
all: [packSavedObjectType],
|
||||
read: [],
|
||||
},
|
||||
ui: ['writePacks', 'readPacks'],
|
||||
},
|
||||
{
|
||||
api: [`${PLUGIN_ID}-readPacks`],
|
||||
id: 'packs_read',
|
||||
includeIn: 'read',
|
||||
name: 'Read',
|
||||
savedObject: {
|
||||
all: [],
|
||||
read: [packSavedObjectType],
|
||||
},
|
||||
ui: ['readPacks'],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
});
|
||||
};
|
||||
|
||||
export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginStart> {
|
||||
private readonly logger: Logger;
|
||||
|
@ -40,10 +209,13 @@ export class OsqueryPlugin implements Plugin<OsqueryPluginSetup, OsqueryPluginSt
|
|||
return {};
|
||||
}
|
||||
|
||||
registerFeatures(plugins.features);
|
||||
|
||||
const router = core.http.createRouter();
|
||||
|
||||
const osqueryContext: OsqueryAppContext = {
|
||||
logFactory: this.context.logger,
|
||||
getStartServices: core.getStartServices,
|
||||
service: this.osqueryAppContextService,
|
||||
config: (): ConfigType => config,
|
||||
security: plugins.security,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
import uuid from 'uuid';
|
||||
import moment from 'moment';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
|
@ -19,6 +20,7 @@ import {
|
|||
} from '../../../common/schemas/routes/action/create_action_request_body_schema';
|
||||
|
||||
import { incrementCount } from '../usage';
|
||||
import { getInternalSavedObjectsClient } from '../../usage/collector';
|
||||
|
||||
export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.post(
|
||||
|
@ -30,10 +32,17 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
|||
CreateActionRequestBodySchema
|
||||
>(createActionRequestBodySchema),
|
||||
},
|
||||
options: {
|
||||
tags: [`access:${PLUGIN_ID}-readLiveQueries`, `access:${PLUGIN_ID}-runSavedQueries`],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const internalSavedObjectsClient = await getInternalSavedObjectsClient(
|
||||
osqueryContext.getStartServices
|
||||
);
|
||||
|
||||
const { agentSelection } = request.body as { agentSelection: AgentSelection };
|
||||
const selectedAgents = await parseAgentSelection(
|
||||
esClient,
|
||||
|
@ -41,12 +50,14 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
|||
osqueryContext,
|
||||
agentSelection
|
||||
);
|
||||
incrementCount(soClient, 'live_query');
|
||||
incrementCount(internalSavedObjectsClient, 'live_query');
|
||||
if (!selectedAgents.length) {
|
||||
incrementCount(soClient, 'live_query', 'errors');
|
||||
incrementCount(internalSavedObjectsClient, 'live_query', 'errors');
|
||||
return response.badRequest({ body: new Error('No agents found for selection') });
|
||||
}
|
||||
|
||||
// TODO: Add check for `runSavedQueries` only
|
||||
|
||||
try {
|
||||
const currentUser = await osqueryContext.security.authc.getCurrentUser(request)?.username;
|
||||
const action = {
|
||||
|
@ -74,7 +85,7 @@ export const createActionRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
|||
},
|
||||
});
|
||||
} catch (error) {
|
||||
incrementCount(soClient, 'live_query', 'errors');
|
||||
incrementCount(internalSavedObjectsClient, 'live_query', 'errors');
|
||||
return response.customError({
|
||||
statusCode: 500,
|
||||
body: new Error(`Error occurred while processing ${error}`),
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const getAgentDetailsRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/fleet_wrapper/agents/{id}',
|
||||
validate: {
|
||||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
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);
|
||||
|
||||
return response.ok({ body: { item: agent } });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const getAgentPoliciesRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/fleet_wrapper/agent_policies',
|
||||
validate: {
|
||||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
query: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
|
||||
const agentPolicies = await osqueryContext.service.getAgentPolicyService()?.list(soClient, {
|
||||
...(request.query || {}),
|
||||
perPage: 100,
|
||||
});
|
||||
|
||||
return response.ok({ body: agentPolicies });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const getAgentPolicyRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/fleet_wrapper/agent_policies/{id}',
|
||||
validate: {
|
||||
params: schema.object({
|
||||
id: schema.string(),
|
||||
}),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
|
||||
const packageInfo = await osqueryContext.service
|
||||
.getAgentPolicyService()
|
||||
?.get(soClient, request.params.id);
|
||||
|
||||
return response.ok({ body: { item: packageInfo } });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { GetAgentStatusResponse } from '../../../../fleet/common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const getAgentStatusForAgentPolicyRoute = (
|
||||
router: IRouter,
|
||||
osqueryContext: OsqueryAppContext
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/fleet_wrapper/agent-status',
|
||||
validate: {
|
||||
query: schema.object({
|
||||
policyId: schema.string(),
|
||||
kuery: schema.maybe(schema.string()),
|
||||
}),
|
||||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
|
||||
const results = await osqueryContext.service
|
||||
.getAgentService()
|
||||
?.getAgentStatusForAgentPolicy(esClient, request.query.policyId, request.query.kuery);
|
||||
|
||||
if (!results) {
|
||||
return response.ok({ body: {} });
|
||||
}
|
||||
|
||||
const body: GetAgentStatusResponse = { results };
|
||||
|
||||
return response.ok({ body });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/fleet_wrapper/agents',
|
||||
validate: {
|
||||
query: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asInternalUser;
|
||||
|
||||
const agents = await osqueryContext.service
|
||||
.getAgentService()
|
||||
// @ts-expect-error update types
|
||||
?.listAgents(esClient, request.query);
|
||||
|
||||
return response.ok({ body: agents });
|
||||
}
|
||||
);
|
||||
};
|
|
@ -6,19 +6,19 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
|
||||
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../fleet/common';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const findScheduledQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
export const getPackagePoliciesRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/scheduled_query',
|
||||
path: '/internal/osquery/fleet_wrapper/package_policies',
|
||||
validate: {
|
||||
query: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const kuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: ${OSQUERY_INTEGRATION_NAME}`;
|
24
x-pack/plugins/osquery/server/routes/fleet_wrapper/index.ts
Normal file
24
x-pack/plugins/osquery/server/routes/fleet_wrapper/index.ts
Normal file
|
@ -0,0 +1,24 @@
|
|||
/*
|
||||
* 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 { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
import { getAgentPoliciesRoute } from './get_agent_policies';
|
||||
import { getAgentPolicyRoute } from './get_agent_policy';
|
||||
import { getAgentStatusForAgentPolicyRoute } from './get_agent_status_for_agent_policy';
|
||||
import { getPackagePoliciesRoute } from './get_package_policies';
|
||||
import { getAgentsRoute } from './get_agents';
|
||||
import { getAgentDetailsRoute } from './get_agent_details';
|
||||
|
||||
export const initFleetWrapperRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
||||
getAgentDetailsRoute(router, context);
|
||||
getAgentPoliciesRoute(router, context);
|
||||
getAgentPolicyRoute(router, context);
|
||||
getAgentStatusForAgentPolicyRoute(router, context);
|
||||
getPackagePoliciesRoute(router, context);
|
||||
getAgentsRoute(router, context);
|
||||
};
|
|
@ -10,13 +10,19 @@ import { initActionRoutes } from './action';
|
|||
import { OsqueryAppContext } from '../lib/osquery_app_context_services';
|
||||
import { initSavedQueryRoutes } from './saved_query';
|
||||
import { initStatusRoutes } from './status';
|
||||
import { initFleetWrapperRoutes } from './fleet_wrapper';
|
||||
import { initPackRoutes } from './pack';
|
||||
import { initScheduledQueryGroupRoutes } from './scheduled_query_group';
|
||||
import { initPrivilegesCheckRoutes } from './privileges_check';
|
||||
|
||||
export const defineRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
||||
const config = context.config();
|
||||
|
||||
initActionRoutes(router, context);
|
||||
initStatusRoutes(router, context);
|
||||
initScheduledQueryGroupRoutes(router, context);
|
||||
initFleetWrapperRoutes(router, context);
|
||||
initPrivilegesCheckRoutes(router, context);
|
||||
|
||||
if (config.packs) {
|
||||
initPackRoutes(router);
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* 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 { IRouter } from '../../../../../../src/core/server';
|
||||
import { privilegesCheckRoute } from './privileges_check_route';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const initPrivilegesCheckRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
||||
privilegesCheckRoute(router, context);
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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 { OSQUERY_INTEGRATION_NAME, PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const privilegesCheckRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/privileges_check',
|
||||
validate: {},
|
||||
options: {
|
||||
tags: [`access:${PLUGIN_ID}-readLiveQueries`],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
||||
|
||||
const privileges = (
|
||||
await esClient.security.hasPrivileges({
|
||||
body: {
|
||||
index: [
|
||||
{
|
||||
names: [`logs-${OSQUERY_INTEGRATION_NAME}.result*`],
|
||||
privileges: ['read'],
|
||||
},
|
||||
],
|
||||
},
|
||||
})
|
||||
).body;
|
||||
|
||||
return response.ok({
|
||||
body: privileges,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import {
|
||||
createSavedQueryRequestSchema,
|
||||
CreateSavedQueryRequestSchemaDecoded,
|
||||
|
@ -24,6 +24,7 @@ export const createSavedQueryRoute = (router: IRouter) => {
|
|||
CreateSavedQueryRequestSchemaDecoded
|
||||
>(createSavedQueryRequestSchema),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-writeSavedQueries`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||
|
||||
|
@ -17,6 +17,7 @@ export const deleteSavedQueryRoute = (router: IRouter) => {
|
|||
validate: {
|
||||
body: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-writeSavedQueries`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||
|
||||
|
@ -17,6 +17,7 @@ export const findSavedQueryRoute = (router: IRouter) => {
|
|||
validate: {
|
||||
query: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-readSavedQueries`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||
|
||||
|
@ -17,6 +17,7 @@ export const readSavedQueryRoute = (router: IRouter) => {
|
|||
validate: {
|
||||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-readSavedQueries`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||
|
||||
|
@ -18,6 +18,7 @@ export const updateSavedQueryRoute = (router: IRouter) => {
|
|||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
body: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-writeSavedQueries`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
|
|
|
@ -6,16 +6,18 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const createScheduledQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
router.post(
|
||||
{
|
||||
path: '/internal/osquery/scheduled',
|
||||
path: '/internal/osquery/scheduled_query_group',
|
||||
validate: {
|
||||
body: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-writePacks`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const esClient = context.core.elasticsearch.client.asCurrentUser;
|
|
@ -6,17 +6,18 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||
|
||||
export const deleteSavedQueryRoute = (router: IRouter) => {
|
||||
router.delete(
|
||||
{
|
||||
path: '/internal/osquery/saved_query',
|
||||
path: '/internal/osquery/scheduled_query_group',
|
||||
validate: {
|
||||
body: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-writePacks`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
|
@ -0,0 +1,38 @@
|
|||
/*
|
||||
* 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 { schema } from '@kbn/config-schema';
|
||||
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../../../fleet/common';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const findScheduledQueryGroupRoute = (
|
||||
router: IRouter,
|
||||
osqueryContext: OsqueryAppContext
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/scheduled_query_group',
|
||||
validate: {
|
||||
query: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-readPacks`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const kuery = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.attributes.package.name: ${OSQUERY_INTEGRATION_NAME}`;
|
||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||
const policies = await packagePolicyService?.list(context.core.savedObjects.client, {
|
||||
kuery,
|
||||
});
|
||||
|
||||
return response.ok({
|
||||
body: policies,
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
|
@ -10,14 +10,14 @@ import { IRouter } from '../../../../../../src/core/server';
|
|||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
// import { createScheduledQueryRoute } from './create_scheduled_query_route';
|
||||
// import { deleteScheduledQueryRoute } from './delete_scheduled_query_route';
|
||||
import { findScheduledQueryRoute } from './find_scheduled_query_route';
|
||||
import { readScheduledQueryRoute } from './read_scheduled_query_route';
|
||||
import { findScheduledQueryGroupRoute } from './find_scheduled_query_group_route';
|
||||
import { readScheduledQueryGroupRoute } from './read_scheduled_query_group_route';
|
||||
// import { updateScheduledQueryRoute } from './update_scheduled_query_route';
|
||||
|
||||
export const initScheduledQueryRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
||||
export const initScheduledQueryGroupRoutes = (router: IRouter, context: OsqueryAppContext) => {
|
||||
// createScheduledQueryRoute(router);
|
||||
// deleteScheduledQueryRoute(router);
|
||||
findScheduledQueryRoute(router, context);
|
||||
readScheduledQueryRoute(router, context);
|
||||
findScheduledQueryGroupRoute(router, context);
|
||||
readScheduledQueryGroupRoute(router, context);
|
||||
// updateScheduledQueryRoute(router);
|
||||
};
|
|
@ -6,28 +6,34 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
export const readScheduledQueryRoute = (router: IRouter, osqueryContext: OsqueryAppContext) => {
|
||||
export const readScheduledQueryGroupRoute = (
|
||||
router: IRouter,
|
||||
osqueryContext: OsqueryAppContext
|
||||
) => {
|
||||
router.get(
|
||||
{
|
||||
path: '/internal/osquery/scheduled_query/{id}',
|
||||
path: '/internal/osquery/scheduled_query_group/{id}',
|
||||
validate: {
|
||||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-readPacks`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
||||
const packagePolicyService = osqueryContext.service.getPackagePolicyService();
|
||||
|
||||
// @ts-expect-error update types
|
||||
const scheduledQuery = await packagePolicyService?.get(savedObjectsClient, request.params.id);
|
||||
const scheduledQueryGroup = await packagePolicyService?.get(
|
||||
savedObjectsClient,
|
||||
// @ts-expect-error update types
|
||||
request.params.id
|
||||
);
|
||||
|
||||
return response.ok({
|
||||
// @ts-expect-error update types
|
||||
body: scheduledQuery,
|
||||
body: { item: scheduledQueryGroup },
|
||||
});
|
||||
}
|
||||
);
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
|
||||
import { PLUGIN_ID } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { savedQuerySavedObjectType } from '../../../common/types';
|
||||
|
||||
|
@ -18,6 +18,7 @@ export const updateSavedQueryRoute = (router: IRouter) => {
|
|||
params: schema.object({}, { unknowns: 'allow' }),
|
||||
body: schema.object({}, { unknowns: 'allow' }),
|
||||
},
|
||||
options: { tags: [`access:${PLUGIN_ID}-writePacks`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const savedObjectsClient = context.core.savedObjects.client;
|
|
@ -5,7 +5,7 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import { OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
import { PLUGIN_ID, OSQUERY_INTEGRATION_NAME } from '../../../common';
|
||||
import { IRouter } from '../../../../../../src/core/server';
|
||||
import { OsqueryAppContext } from '../../lib/osquery_app_context_services';
|
||||
|
||||
|
@ -14,16 +14,10 @@ export const createStatusRoute = (router: IRouter, osqueryContext: OsqueryAppCon
|
|||
{
|
||||
path: '/internal/osquery/status',
|
||||
validate: false,
|
||||
options: { tags: [`access:${PLUGIN_ID}-read`] },
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const isSuperUser = osqueryContext.security.authc
|
||||
.getCurrentUser(request)
|
||||
?.roles.includes('superuser');
|
||||
|
||||
if (!isSuperUser) {
|
||||
return response.ok({ body: undefined });
|
||||
}
|
||||
|
||||
const packageInfo = await osqueryContext.service
|
||||
.getPackageService()
|
||||
|
|
|
@ -23,6 +23,6 @@ export const usageMetricSavedObjectMappings: SavedObjectsType['mappings'] = {
|
|||
export const usageMetricType: SavedObjectsType = {
|
||||
name: usageMetricSavedObjectType,
|
||||
hidden: false,
|
||||
namespaceType: 'single',
|
||||
namespaceType: 'agnostic',
|
||||
mappings: usageMetricSavedObjectMappings,
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ import { OsqueryFactory } from './factory/types';
|
|||
export const osquerySearchStrategyProvider = <T extends FactoryQueryTypes>(
|
||||
data: PluginStart
|
||||
): ISearchStrategy<StrategyRequestType<T>, StrategyResponseType<T>> => {
|
||||
const es = data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY);
|
||||
let es: typeof data.search.searchAsInternalUser;
|
||||
|
||||
return {
|
||||
search: (request, options, deps) => {
|
||||
|
@ -32,20 +32,35 @@ export const osquerySearchStrategyProvider = <T extends FactoryQueryTypes>(
|
|||
}
|
||||
const queryFactory: OsqueryFactory<T> = osqueryFactory[request.factoryQueryType];
|
||||
const dsl = queryFactory.buildDsl(request);
|
||||
return es.search({ ...request, params: dsl }, options, deps).pipe(
|
||||
map((response) => {
|
||||
return {
|
||||
...response,
|
||||
...{
|
||||
rawResponse: shimHitsTotal(response.rawResponse),
|
||||
},
|
||||
};
|
||||
}),
|
||||
mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes))
|
||||
);
|
||||
|
||||
// use internal user for searching .fleet* indicies
|
||||
es = dsl.index?.includes('fleet')
|
||||
? data.search.searchAsInternalUser
|
||||
: data.search.getSearchStrategy(ENHANCED_ES_SEARCH_STRATEGY);
|
||||
|
||||
return es
|
||||
.search(
|
||||
{
|
||||
...request,
|
||||
params: dsl,
|
||||
},
|
||||
options,
|
||||
deps
|
||||
)
|
||||
.pipe(
|
||||
map((response) => {
|
||||
return {
|
||||
...response,
|
||||
...{
|
||||
rawResponse: shimHitsTotal(response.rawResponse),
|
||||
},
|
||||
};
|
||||
}),
|
||||
mergeMap((esSearchRes) => queryFactory.parse(request, esSearchRes))
|
||||
);
|
||||
},
|
||||
cancel: async (id, options, deps) => {
|
||||
if (es.cancel) {
|
||||
if (es?.cancel) {
|
||||
return es.cancel(id, options, deps);
|
||||
}
|
||||
},
|
||||
|
|
|
@ -11,11 +11,12 @@ import { getBeatUsage, getLiveQueryUsage, getPolicyLevelUsage } from './fetchers
|
|||
import { CollectorDependencies, usageSchema, UsageData } from './types';
|
||||
|
||||
export type RegisterCollector = (deps: CollectorDependencies) => void;
|
||||
export async function getInternalSavedObjectsClient(core: CoreSetup) {
|
||||
return core.getStartServices().then(async ([coreStart]) => {
|
||||
return coreStart.savedObjects.createInternalRepository();
|
||||
});
|
||||
}
|
||||
export const getInternalSavedObjectsClient = async (
|
||||
getStartServices: CoreSetup['getStartServices']
|
||||
) => {
|
||||
const [coreStart] = await getStartServices();
|
||||
return new SavedObjectsClient(coreStart.savedObjects.createInternalRepository());
|
||||
};
|
||||
|
||||
export const registerCollector: RegisterCollector = ({ core, osqueryContext, usageCollection }) => {
|
||||
if (!usageCollection) {
|
||||
|
@ -26,7 +27,8 @@ export const registerCollector: RegisterCollector = ({ core, osqueryContext, usa
|
|||
schema: usageSchema,
|
||||
isReady: () => true,
|
||||
fetch: async ({ esClient }: CollectorFetchContext): Promise<UsageData> => {
|
||||
const savedObjectsClient = new SavedObjectsClient(await getInternalSavedObjectsClient(core));
|
||||
const savedObjectsClient = await getInternalSavedObjectsClient(core.getStartServices);
|
||||
|
||||
return {
|
||||
beat_metrics: {
|
||||
usage: await getBeatUsage(esClient),
|
||||
|
|
|
@ -115,6 +115,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
'logs',
|
||||
'maps',
|
||||
'observabilityCases',
|
||||
'osquery',
|
||||
'uptime',
|
||||
'siem',
|
||||
'fleet',
|
||||
|
|
|
@ -70,6 +70,19 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
indexPatterns: ['all', 'read'],
|
||||
savedObjectsManagement: ['all', 'read'],
|
||||
timelion: ['all', 'read'],
|
||||
osquery: [
|
||||
'all',
|
||||
'read',
|
||||
'minimal_all',
|
||||
'minimal_read',
|
||||
'live_queries_all',
|
||||
'live_queries_read',
|
||||
'run_saved_queries',
|
||||
'saved_queries_all',
|
||||
'saved_queries_read',
|
||||
'packs_all',
|
||||
'packs_read',
|
||||
],
|
||||
},
|
||||
reserved: ['ml_user', 'ml_admin', 'ml_apm_user', 'monitoring'],
|
||||
};
|
||||
|
|
|
@ -37,6 +37,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
logs: ['all', 'read'],
|
||||
uptime: ['all', 'read'],
|
||||
apm: ['all', 'read'],
|
||||
osquery: ['all', 'read'],
|
||||
ml: ['all', 'read'],
|
||||
siem: ['all', 'read'],
|
||||
fleet: ['all', 'read'],
|
||||
|
|
|
@ -53,6 +53,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
|
|||
catalogueId !== 'ml' &&
|
||||
catalogueId !== 'ml_file_data_visualizer' &&
|
||||
catalogueId !== 'monitoring' &&
|
||||
catalogueId !== 'osquery' &&
|
||||
!esFeatureExceptions.includes(catalogueId)
|
||||
);
|
||||
expect(uiCapabilities.value!.catalogue).to.eql(expected);
|
||||
|
@ -74,6 +75,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
|
|||
'appSearch',
|
||||
'workplaceSearch',
|
||||
'spaces',
|
||||
'osquery',
|
||||
...esFeatureExceptions,
|
||||
];
|
||||
const expected = mapValues(
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
|
|||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(
|
||||
navLinksBuilder.except('ml', 'monitoring')
|
||||
navLinksBuilder.except('ml', 'monitoring', 'osquery')
|
||||
);
|
||||
break;
|
||||
case 'everything_space_all at everything_space':
|
||||
|
@ -57,7 +57,8 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
|
|||
'monitoring',
|
||||
'enterpriseSearch',
|
||||
'appSearch',
|
||||
'workplaceSearch'
|
||||
'workplaceSearch',
|
||||
'osquery'
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
|
|
@ -53,6 +53,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
|
|||
catalogueId !== 'ml' &&
|
||||
catalogueId !== 'monitoring' &&
|
||||
catalogueId !== 'ml_file_data_visualizer' &&
|
||||
catalogueId !== 'osquery' &&
|
||||
!esFeatureExceptions.includes(catalogueId)
|
||||
);
|
||||
expect(uiCapabilities.value!.catalogue).to.eql(expected);
|
||||
|
@ -70,6 +71,7 @@ export default function catalogueTests({ getService }: FtrProviderContext) {
|
|||
'enterpriseSearch',
|
||||
'appSearch',
|
||||
'workplaceSearch',
|
||||
'osquery',
|
||||
...esFeatureExceptions,
|
||||
];
|
||||
const expected = mapValues(
|
||||
|
|
|
@ -42,7 +42,7 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
|
|||
expect(uiCapabilities.success).to.be(true);
|
||||
expect(uiCapabilities.value).to.have.property('navLinks');
|
||||
expect(uiCapabilities.value!.navLinks).to.eql(
|
||||
navLinksBuilder.except('ml', 'monitoring')
|
||||
navLinksBuilder.except('ml', 'monitoring', 'osquery')
|
||||
);
|
||||
break;
|
||||
case 'read':
|
||||
|
@ -55,7 +55,8 @@ export default function navLinksTests({ getService }: FtrProviderContext) {
|
|||
'monitoring',
|
||||
'enterpriseSearch',
|
||||
'appSearch',
|
||||
'workplaceSearch'
|
||||
'workplaceSearch',
|
||||
'osquery'
|
||||
)
|
||||
);
|
||||
break;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue