[Defend Workflows][Osquery] New live query policy lookup (#166615)

https://github.com/elastic/kibana/issues/166268

closes https://github.com/elastic/security-team/issues/7676

Aggregations that return policy list that is being used for looking up
search term defaults to 10 results when no `size` param passed. At this
point size is set to `2000`.

Before


7571378e-e1e9-4aa9-a179-e17fe50c502e

After 


56a395e1-f9a9-4cf9-90f8-07d4758b8136


Added callout informing user that no agents are available, copy provided
here - https://github.com/elastic/security-team/issues/7676

![Screenshot 2023-09-21 at 11 31
24](1c44db9b-5bc3-4737-8fed-ed4ff56e018b)
This commit is contained in:
Konrad Szwarc 2023-09-21 12:47:14 +02:00 committed by GitHub
parent b04b07ec52
commit f50edde37e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 82 additions and 28 deletions

View file

@ -8,7 +8,7 @@
import { takeOsqueryActionWithParams } from '../../tasks/live_query';
import { ServerlessRoleName } from '../../support/roles';
describe('ALL - Timelines', { tags: ['@ess'] }, () => {
describe.skip('ALL - Timelines', { tags: ['@ess'] }, () => {
beforeEach(() => {
cy.login(ServerlessRoleName.SOC_MANAGER);
});

View file

@ -7,10 +7,20 @@
import { find } from 'lodash/fp';
import React, { useCallback, useEffect, useRef, useState } from 'react';
import { EuiComboBox, EuiHealth, EuiFormRow, EuiHighlight, EuiSpacer } from '@elastic/eui';
import {
EuiComboBox,
EuiHealth,
EuiFormRow,
EuiHighlight,
EuiSpacer,
EuiCallOut,
EuiLink,
} from '@elastic/eui';
import deepEqual from 'fast-deep-equal';
import useDebounce from 'react-use/lib/useDebounce';
import { FormattedMessage } from '@kbn/i18n-react';
import { useKibana } from '../common/lib/kibana';
import { useAllAgents } from './use_all_agents';
import { useAgentGroups } from './use_agent_groups';
import { AgentGrouper } from './agent_grouper';
@ -27,6 +37,7 @@ import {
ALL_AGENTS_LABEL,
AGENT_POLICY_LABEL,
AGENT_SELECTION_LABEL,
NO_AGENT_AVAILABLE_TITLE,
} from './translations';
import type { SelectedGroups, AgentOptionValue, GroupOption, AgentSelection } from './types';
@ -42,6 +53,7 @@ const perPage = 10;
const DEBOUNCE_DELAY = 300; // ms
const AgentsTableComponent: React.FC<AgentsTableProps> = ({ agentSelection, onChange, error }) => {
const { docLinks } = useKibana().services;
// search related
const [searchValue, setSearchValue] = useState<string>('');
const [modifyingSearch, setModifyingSearch] = useState<boolean>(false);
@ -148,17 +160,23 @@ const AgentsTableComponent: React.FC<AgentsTableProps> = ({ agentSelection, onCh
useEffect(() => {
if (agentsFetched && groupsFetched && agentGroupsData) {
// Cap policies to 10 on init dropdown
const policies = (agentGroupsData?.groups.policies || []).slice(
0,
searchValue === '' ? 10 : undefined
);
const grouper = new AgentGrouper();
// update the groups when groups or agents have changed
grouper.setTotalAgents(agentGroupsData?.total);
grouper.updateGroup(AGENT_GROUP_KEY.Platform, agentGroupsData?.groups.platforms);
grouper.updateGroup(AGENT_GROUP_KEY.Policy, agentGroupsData?.groups.policies);
grouper.updateGroup(AGENT_GROUP_KEY.Policy, policies);
// @ts-expect-error update types
grouper.updateGroup(AGENT_GROUP_KEY.Agent, agents);
const newOptions = grouper.generateOptions();
setOptions((prevOptions) => (!deepEqual(prevOptions, newOptions) ? newOptions : prevOptions));
}
}, [groupsLoading, agents, agentsFetched, groupsFetched, agentGroupsData]);
}, [groupsLoading, agents, agentsFetched, groupsFetched, agentGroupsData, searchValue]);
const renderOption = useCallback((option, searchVal, contentClassName) => {
const { label, value } = option;
@ -183,22 +201,58 @@ const AgentsTableComponent: React.FC<AgentsTableProps> = ({ agentSelection, onCh
setModifyingSearch(v !== '');
setSearchValue(v);
}, []);
const isFetched = groupsFetched && agentsFetched && agentGroupsData;
const renderNoAgentAvailableWarning = () => {
if (isFetched && !options.length) {
return (
<>
<EuiCallOut color="danger" size="s" iconType="warning" title={NO_AGENT_AVAILABLE_TITLE}>
<FormattedMessage
id="xpack.osquery.agents.noAgentAvailableDescription"
defaultMessage="Before you can query agents, they must be enrolled in an agent policy with the Osquery integration installed. Refer to {docsLink} for more information."
// eslint-disable-next-line react-perf/jsx-no-new-object-as-prop
values={{
docsLink: (
<EuiLink
href={`${docLinks.links.fleet.agentPolicy}#apply-a-policy`}
target={'_blank'}
>
<FormattedMessage
id="xpack.osquery.agents.noAgentAvailableDescription.docsLink"
defaultMessage="Apply a policy"
/>
</EuiLink>
),
}}
/>
</EuiCallOut>
<EuiSpacer size="s" />
</>
);
}
return null;
};
return (
<div>
<EuiFormRow label={AGENT_SELECTION_LABEL} fullWidth isInvalid={!!error} error={error}>
<EuiComboBox
data-test-subj="agentSelection"
placeholder={SELECT_AGENT_LABEL}
isLoading={modifyingSearch || groupsLoading || agentsLoading}
options={options}
isClearable={true}
fullWidth={true}
onSearchChange={onSearchChange}
selectedOptions={selectedOptions}
onChange={onSelection}
renderOption={renderOption}
/>
<>
{renderNoAgentAvailableWarning()}
<EuiComboBox
data-test-subj="agentSelection"
placeholder={SELECT_AGENT_LABEL}
isLoading={groupsLoading || agentsLoading || modifyingSearch}
options={options}
isClearable={true}
fullWidth={true}
onSearchChange={onSearchChange}
selectedOptions={selectedOptions}
onChange={onSelection}
renderOption={renderOption}
/>
</>
</EuiFormRow>
<EuiSpacer size="xs" />
{numAgentsSelected > 0 ? <span>{generateSelectedAgentsMessage(numAgentsSelected)}</span> : ''}

View file

@ -42,6 +42,14 @@ export const AGENT = i18n.translate('xpack.osquery.agents.agent', {
export const AGENT_SELECTION_LABEL = i18n.translate('xpack.osquery.agents.selectionLabel', {
defaultMessage: `Agents`,
});
export const NO_AGENT_AVAILABLE_TITLE = i18n.translate(
'xpack.osquery.agents.noAgentAvailableTitle',
{
defaultMessage: `No agents available`,
}
);
export const AGENT_QUERY = i18n.translate('xpack.osquery.agents.query', {
defaultMessage: `Query`,
});

View file

@ -13,7 +13,7 @@ import type { AgentsRequestOptions } from '../../../../../common/search_strategy
export const buildAgentsQuery = ({
kuery,
pagination: { cursorStart, querySize },
pagination: { cursorStart },
sort,
}: AgentsRequestOptions): ISearchRequestParams => {
const activeQuery = `active: true`;
@ -24,7 +24,7 @@ export const buildAgentsQuery = ({
const filterQuery = getQueryFilter({ filter });
const dslQuery = {
return {
allow_no_indices: true,
index: AGENTS_INDEX,
ignore_unavailable: true,
@ -39,17 +39,11 @@ export const buildAgentsQuery = ({
terms: {
field: 'local_metadata.os.platform',
},
aggs: {
policies: {
terms: {
field: 'policy_id',
},
},
},
},
policies: {
terms: {
field: 'policy_id',
size: 2000,
},
},
},
@ -61,10 +55,8 @@ export const buildAgentsQuery = ({
},
},
],
size: querySize,
size: 0,
from: cursorStart,
},
};
return dslQuery;
};