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