mirror of
https://github.com/elastic/kibana.git
synced 2025-06-27 18:51:07 -04:00
[UII] Hide agentless agents and policies (#219175)
This commit is contained in:
parent
168eef7949
commit
6c2cbf10a6
28 changed files with 268 additions and 32 deletions
|
@ -20092,6 +20092,15 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "showAgentless",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "showInactive",
|
||||
|
|
|
@ -20092,6 +20092,15 @@
|
|||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "showAgentless",
|
||||
"required": false,
|
||||
"schema": {
|
||||
"default": true,
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
{
|
||||
"in": "query",
|
||||
"name": "showInactive",
|
||||
|
|
|
@ -23025,6 +23025,12 @@ paths:
|
|||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: showAgentless
|
||||
required: false
|
||||
schema:
|
||||
default: true
|
||||
type: boolean
|
||||
- in: query
|
||||
name: showInactive
|
||||
required: false
|
||||
|
|
|
@ -25267,6 +25267,12 @@ paths:
|
|||
required: false
|
||||
schema:
|
||||
type: string
|
||||
- in: query
|
||||
name: showAgentless
|
||||
required: false
|
||||
schema:
|
||||
default: true
|
||||
type: boolean
|
||||
- in: query
|
||||
name: showInactive
|
||||
required: false
|
||||
|
|
|
@ -42,6 +42,7 @@ export const AGENT_POLICY_MAPPINGS = {
|
|||
unenroll_timeout: { type: 'integer' },
|
||||
updated_at: { type: 'date' },
|
||||
updated_by: { type: 'keyword' },
|
||||
supports_agentless: { type: 'boolean' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@ import type { ListResult, ListWithKuery } from './common';
|
|||
|
||||
export interface GetAgentsRequest {
|
||||
query: ListWithKuery & {
|
||||
showAgentless?: boolean;
|
||||
showInactive?: boolean;
|
||||
showUpgradeable?: boolean;
|
||||
withMetrics?: boolean;
|
||||
|
|
|
@ -409,6 +409,13 @@ describe('getFieldSpecs', () => {
|
|||
searchable: true,
|
||||
type: 'string',
|
||||
},
|
||||
{
|
||||
aggregatable: true,
|
||||
esTypes: ['boolean'],
|
||||
name: 'ingest-agent-policies.supports_agentless',
|
||||
searchable: true,
|
||||
type: 'boolean',
|
||||
},
|
||||
]
|
||||
);
|
||||
});
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*/
|
||||
|
||||
import React, { useMemo, useState, useCallback } from 'react';
|
||||
import { Redirect, useRouteMatch, useLocation } from 'react-router-dom';
|
||||
import { Redirect, useRouteMatch } from 'react-router-dom';
|
||||
import { Routes, Route } from '@kbn/shared-ux-router';
|
||||
|
||||
import { i18n } from '@kbn/i18n';
|
||||
|
@ -24,6 +24,7 @@ import {
|
|||
useStartServices,
|
||||
useFleetStatus,
|
||||
useIntraAppState,
|
||||
useUrlParams,
|
||||
} from '../../../hooks';
|
||||
import { Loading, Error, AgentEnrollmentFlyout } from '../../../components';
|
||||
import { WithHeaderLayout } from '../../../layouts';
|
||||
|
@ -40,12 +41,20 @@ export const AgentPolicyDetailsPage: React.FunctionComponent = () => {
|
|||
params: { policyId, tabId = '' },
|
||||
} = useRouteMatch<{ policyId: string; tabId?: string }>();
|
||||
const { getHref } = useLink();
|
||||
const { urlParams } = useUrlParams();
|
||||
const showAgentless = urlParams.showAgentless === 'true';
|
||||
|
||||
const agentPolicyRequest = useGetOneAgentPolicy(policyId);
|
||||
const agentPolicy = agentPolicyRequest.data ? agentPolicyRequest.data.item : null;
|
||||
// If the agent policy is agentless, hide it by default unless `showAgentless` url param is true
|
||||
const agentPolicy =
|
||||
agentPolicyRequest.data?.item &&
|
||||
(!agentPolicyRequest.data.item.supports_agentless || showAgentless)
|
||||
? agentPolicyRequest.data.item
|
||||
: null;
|
||||
const { isLoading, error, sendRequest: refreshAgentPolicy } = agentPolicyRequest;
|
||||
const queryParams = new URLSearchParams(useLocation().search);
|
||||
const openEnrollmentFlyoutOpenByDefault = queryParams.get('openEnrollmentFlyout') === 'true';
|
||||
const openAddAgentHelpPopoverOpenByDefault = queryParams.get('showAddAgentHelp') === 'true';
|
||||
|
||||
const openEnrollmentFlyoutOpenByDefault = urlParams.openEnrollmentFlyout === 'true';
|
||||
const openAddAgentHelpPopoverOpenByDefault = urlParams.showAddAgentHelp === 'true';
|
||||
const [redirectToAgentPolicyList] = useState<boolean>(false);
|
||||
const [isEnrollmentFlyoutOpen, setIsEnrollmentFlyoutOpen] = useState<boolean>(
|
||||
openEnrollmentFlyoutOpenByDefault
|
||||
|
|
|
@ -26,7 +26,6 @@ import { useHistory } from 'react-router-dom';
|
|||
import type { AgentPolicy } from '../../../types';
|
||||
import { getRootIntegrations } from '../../../../../../common/services';
|
||||
import {
|
||||
AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
|
||||
INGEST_SAVED_OBJECT_INDEX,
|
||||
} from '../../../constants';
|
||||
|
@ -39,7 +38,6 @@ import {
|
|||
useUrlParams,
|
||||
useBreadcrumbs,
|
||||
useGetAgentPoliciesQuery,
|
||||
useFleetStatus,
|
||||
} from '../../../hooks';
|
||||
import { SearchBar } from '../../../components';
|
||||
import { AgentPolicySummaryLine } from '../../../../../components';
|
||||
|
@ -48,17 +46,17 @@ import { LinkedAgentCount, AgentPolicyActionMenu } from '../components';
|
|||
import { CreateAgentPolicyFlyout } from './components';
|
||||
|
||||
export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
||||
const { isSpaceAwarenessEnabled } = useFleetStatus();
|
||||
useBreadcrumbs('policies_list');
|
||||
const { getPath } = useLink();
|
||||
const hasFleetAllAgentPoliciesPrivileges = useAuthz().fleet.allAgentPolicies;
|
||||
|
||||
const agentPolicySavedObjectType = LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE;
|
||||
const {
|
||||
agents: { enabled: isFleetEnabled },
|
||||
} = useConfig();
|
||||
|
||||
// Table and search states
|
||||
const { urlParams, toUrlParams } = useUrlParams();
|
||||
const showAgentless = urlParams.showAgentless === 'true';
|
||||
const [search, setSearch] = useState<string>(
|
||||
Array.isArray(urlParams.kuery)
|
||||
? urlParams.kuery[urlParams.kuery.length - 1]
|
||||
|
@ -87,6 +85,15 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
|||
[getPath, history, isCreateAgentPolicyFlyoutOpen, toUrlParams, urlParams]
|
||||
);
|
||||
|
||||
// Hide agentless policies by default unless `showAgentless` url param is true
|
||||
const getSearchWithDefaults = (newSearch: string) => {
|
||||
if (showAgentless) {
|
||||
return newSearch;
|
||||
}
|
||||
const defaultSearch = `NOT ${agentPolicySavedObjectType}.supports_agentless:true`;
|
||||
return newSearch.trim() ? `(${defaultSearch}) AND (${newSearch})` : defaultSearch;
|
||||
};
|
||||
|
||||
// Fetch agent policies
|
||||
const {
|
||||
isLoading,
|
||||
|
@ -97,7 +104,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
|||
perPage: pagination.pageSize,
|
||||
sortField: sorting?.field,
|
||||
sortOrder: sorting?.direction,
|
||||
kuery: search,
|
||||
kuery: getSearchWithDefaults(search),
|
||||
withAgentCount: true, // Explicitly fetch agent count
|
||||
full: true,
|
||||
});
|
||||
|
@ -328,11 +335,7 @@ export const AgentPolicyListPage: React.FunctionComponent<{}> = () => {
|
|||
<SearchBar
|
||||
value={search}
|
||||
indexPattern={INGEST_SAVED_OBJECT_INDEX}
|
||||
fieldPrefix={
|
||||
isSpaceAwarenessEnabled
|
||||
? AGENT_POLICY_SAVED_OBJECT_TYPE
|
||||
: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE
|
||||
}
|
||||
fieldPrefix={agentPolicySavedObjectType}
|
||||
onChange={(newSearch) => {
|
||||
setPagination({
|
||||
...pagination,
|
||||
|
|
|
@ -23,6 +23,7 @@ import {
|
|||
useBreadcrumbs,
|
||||
useStartServices,
|
||||
useIntraAppState,
|
||||
useUrlParams,
|
||||
} from '../../../hooks';
|
||||
import { WithHeaderLayout } from '../../../layouts';
|
||||
|
||||
|
@ -40,6 +41,8 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
|
|||
params: { agentId, tabId = '' },
|
||||
} = useRouteMatch<{ agentId: string; tabId?: string }>();
|
||||
const { getHref } = useLink();
|
||||
const { urlParams } = useUrlParams();
|
||||
const showAgentless = urlParams.showAgentless === 'true';
|
||||
const {
|
||||
isLoading,
|
||||
isInitialRequest,
|
||||
|
@ -71,7 +74,12 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
|
|||
}
|
||||
}, [routeState, navigateToApp]);
|
||||
|
||||
const host = agentData?.item?.local_metadata?.host;
|
||||
const agent =
|
||||
agentData?.item &&
|
||||
(showAgentless || !agentData.item.local_metadata?.host?.hostname?.startsWith('agentless-')) // Hide agentless agents
|
||||
? agentData.item
|
||||
: null;
|
||||
const host = agent && agent.local_metadata?.host;
|
||||
|
||||
const headerLeftContent = useMemo(
|
||||
() => (
|
||||
|
@ -110,14 +118,14 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
|
|||
|
||||
const headerRightContent = useMemo(
|
||||
() =>
|
||||
agentData && agentData.item ? (
|
||||
agent ? (
|
||||
<>
|
||||
<EuiSpacer size="m" />
|
||||
<EuiFlexGroup justifyContent="flexEnd" alignItems="center" gutterSize="s" direction="row">
|
||||
{!isAgentPolicyLoading && (
|
||||
<EuiFlexItem grow={false}>
|
||||
<AgentDetailsActionMenu
|
||||
agent={agentData.item}
|
||||
agent={agent}
|
||||
agentPolicy={agentPolicyData?.item}
|
||||
assignFlyoutOpenByDefault={openReassignFlyoutOpenByDefault}
|
||||
onCancelReassign={
|
||||
|
@ -199,8 +207,8 @@ export const AgentDetailsPage: React.FunctionComponent = () => {
|
|||
}
|
||||
error={error}
|
||||
/>
|
||||
) : agentData && agentData.item ? (
|
||||
<AgentDetailsPageContent agent={agentData.item} agentPolicy={agentPolicyData?.item} />
|
||||
) : agent ? (
|
||||
<AgentDetailsPageContent agent={agent} agentPolicy={agentPolicyData?.item} />
|
||||
) : (
|
||||
<Error
|
||||
title={
|
||||
|
|
|
@ -101,6 +101,7 @@ export function useFetchAgentsData() {
|
|||
|
||||
const history = useHistory();
|
||||
const { urlParams, toUrlParams } = useUrlParams();
|
||||
const showAgentless = urlParams.showAgentless === 'true';
|
||||
const defaultKuery: string = (urlParams.kuery as string) || '';
|
||||
const urlHasInactive = (urlParams.showInactive as string) === 'true';
|
||||
|
||||
|
@ -213,6 +214,7 @@ export function useFetchAgentsData() {
|
|||
kuery: kuery && kuery !== '' ? kuery : undefined,
|
||||
sortField: getSortFieldForAPI(sortField),
|
||||
sortOrder,
|
||||
showAgentless,
|
||||
showInactive,
|
||||
showUpgradeable,
|
||||
getStatusSummary: true,
|
||||
|
@ -358,6 +360,7 @@ export function useFetchAgentsData() {
|
|||
kuery,
|
||||
sortField,
|
||||
sortOrder,
|
||||
showAgentless,
|
||||
showInactive,
|
||||
showUpgradeable,
|
||||
fullAgentPolicyFecher,
|
||||
|
|
|
@ -203,6 +203,7 @@ export const getAgentsHandler: FleetRequestHandler<
|
|||
const agentRes = await agentClient.asCurrentUser.listAgents({
|
||||
page: request.query.page,
|
||||
perPage: request.query.perPage,
|
||||
showAgentless: request.query.showAgentless,
|
||||
showInactive: request.query.showInactive,
|
||||
showUpgradeable: request.query.showUpgradeable,
|
||||
kuery: request.query.kuery,
|
||||
|
|
|
@ -227,6 +227,7 @@ export abstract class ActionRunner {
|
|||
|
||||
return getAgentsByKuery(this.esClient, this.soClient, {
|
||||
kuery,
|
||||
showAgentless: this.actionParams.showAgentless,
|
||||
showInactive: this.actionParams.showInactive ?? false,
|
||||
page: 1,
|
||||
perPage,
|
||||
|
|
|
@ -99,7 +99,9 @@ describe('AgentService', () => {
|
|||
});
|
||||
|
||||
it('rejects on listAgents', async () => {
|
||||
await expect(agentClient.listAgents({ showInactive: true })).rejects.toThrowError(
|
||||
await expect(
|
||||
agentClient.listAgents({ showAgentless: true, showInactive: true })
|
||||
).rejects.toThrowError(
|
||||
new FleetUnauthorizedError(
|
||||
`User does not have adequate permissions to access Fleet agents.`
|
||||
)
|
||||
|
@ -224,10 +226,11 @@ function expectApisToCallServicesSuccessfully(
|
|||
|
||||
test('client.listAgents calls getAgentsByKuery and returns results', async () => {
|
||||
mockGetAgentsByKuery.mockResolvedValue('getAgentsByKuery success');
|
||||
await expect(agentClient.listAgents({ showInactive: true })).resolves.toEqual(
|
||||
'getAgentsByKuery success'
|
||||
);
|
||||
await expect(
|
||||
agentClient.listAgents({ showAgentless: true, showInactive: true })
|
||||
).resolves.toEqual('getAgentsByKuery success');
|
||||
expect(mockGetAgentsByKuery).toHaveBeenCalledWith(mockEsClient, mockSoClient, {
|
||||
showAgentless: true,
|
||||
showInactive: true,
|
||||
spaceId,
|
||||
});
|
||||
|
|
|
@ -89,6 +89,7 @@ export interface AgentClient {
|
|||
*/
|
||||
listAgents(
|
||||
options: ListWithKuery & {
|
||||
showAgentless?: boolean;
|
||||
showInactive: boolean;
|
||||
aggregations?: Record<string, AggregationsAggregationContainer>;
|
||||
searchAfter?: SortResults;
|
||||
|
@ -134,6 +135,7 @@ class AgentClientImpl implements AgentClient {
|
|||
|
||||
public async listAgents(
|
||||
options: ListWithKuery & {
|
||||
showAgentless?: boolean;
|
||||
showInactive: boolean;
|
||||
aggregations?: Record<string, AggregationsAggregationContainer>;
|
||||
searchAfter?: SortResults;
|
||||
|
|
|
@ -181,6 +181,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2', '3', '4', '5', 'up', '7'], 7, 'inactive'))
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: true,
|
||||
showInactive: false,
|
||||
page: 1,
|
||||
|
@ -212,6 +213,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5', 'up2', '7'], 7, 'inactive'))
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: true,
|
||||
showInactive: false,
|
||||
page: 1,
|
||||
|
@ -250,6 +252,7 @@ describe('Agents CRUD test', () => {
|
|||
)
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: true,
|
||||
showInactive: false,
|
||||
page: 2,
|
||||
|
@ -277,6 +280,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5'], 10001, 'inactive'))
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: true,
|
||||
showInactive: false,
|
||||
page: 1,
|
||||
|
@ -307,6 +311,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5'], 100, 'updating'))
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: true,
|
||||
showInactive: false,
|
||||
getStatusSummary: true,
|
||||
|
@ -344,6 +349,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2', '3', 'up', '5'], 10001, 'updating'))
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: true,
|
||||
showInactive: false,
|
||||
getStatusSummary: true,
|
||||
|
@ -377,6 +383,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['6', '7'], 7, 'inactive'))
|
||||
);
|
||||
const result = await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showUpgradeable: false,
|
||||
showInactive: false,
|
||||
page: 2,
|
||||
|
@ -411,6 +418,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2'], 2, 'inactive'))
|
||||
);
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: false,
|
||||
});
|
||||
|
||||
|
@ -425,6 +433,7 @@ describe('Agents CRUD test', () => {
|
|||
Promise.resolve(getEsResponse(['1', '2'], 2, 'inactive'))
|
||||
);
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: false,
|
||||
sortField: 'policy_id',
|
||||
});
|
||||
|
@ -437,6 +446,7 @@ describe('Agents CRUD test', () => {
|
|||
});
|
||||
it('should add inactive and unenrolled filter', async () => {
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: false,
|
||||
kuery: '',
|
||||
});
|
||||
|
@ -448,6 +458,7 @@ describe('Agents CRUD test', () => {
|
|||
|
||||
it('should add unenrolled filter', async () => {
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: true,
|
||||
kuery: '',
|
||||
});
|
||||
|
@ -459,6 +470,7 @@ describe('Agents CRUD test', () => {
|
|||
|
||||
it('should not add unenrolled filter', async () => {
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: true,
|
||||
kuery: 'status:unenrolled',
|
||||
});
|
||||
|
@ -470,6 +482,7 @@ describe('Agents CRUD test', () => {
|
|||
|
||||
it('should add inactive filter', async () => {
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: false,
|
||||
kuery: 'status:*',
|
||||
});
|
||||
|
@ -481,6 +494,7 @@ describe('Agents CRUD test', () => {
|
|||
|
||||
it('should not add inactive filter', async () => {
|
||||
await getAgentsByKuery(esClientMock, soClientMock, {
|
||||
showAgentless: true,
|
||||
showInactive: true,
|
||||
kuery: 'status:*',
|
||||
});
|
||||
|
|
|
@ -20,7 +20,7 @@ import type { AgentStatus, FleetServerAgent } from '../../../common/types';
|
|||
import { SO_SEARCH_LIMIT } from '../../../common/constants';
|
||||
import { getSortConfig } from '../../../common';
|
||||
import { isAgentUpgradeAvailable } from '../../../common/services';
|
||||
import { AGENTS_INDEX } from '../../constants';
|
||||
import { AGENTS_INDEX, LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../constants';
|
||||
import {
|
||||
FleetError,
|
||||
isESClientError,
|
||||
|
@ -89,6 +89,7 @@ export type GetAgentsOptions =
|
|||
}
|
||||
| {
|
||||
kuery: string;
|
||||
showAgentless?: boolean;
|
||||
showInactive?: boolean;
|
||||
perPage?: number;
|
||||
};
|
||||
|
@ -107,6 +108,7 @@ export async function getAgents(
|
|||
agents = (
|
||||
await getAllAgentsByKuery(esClient, soClient, {
|
||||
kuery: options.kuery,
|
||||
showAgentless: options.showAgentless,
|
||||
showInactive: options.showInactive ?? false,
|
||||
})
|
||||
).agents;
|
||||
|
@ -200,6 +202,7 @@ export async function getAgentsByKuery(
|
|||
esClient: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract,
|
||||
options: ListWithKuery & {
|
||||
showAgentless?: boolean;
|
||||
showInactive: boolean;
|
||||
spaceId?: string;
|
||||
getStatusSummary?: boolean;
|
||||
|
@ -225,6 +228,7 @@ export async function getAgentsByKuery(
|
|||
sortField = options.sortField ?? 'enrolled_at',
|
||||
sortOrder = options.sortOrder ?? 'desc',
|
||||
kuery,
|
||||
showAgentless = true,
|
||||
showInactive = false,
|
||||
getStatusSummary = false,
|
||||
showUpgradeable,
|
||||
|
@ -241,9 +245,26 @@ export async function getAgentsByKuery(
|
|||
filters.push(kuery);
|
||||
}
|
||||
|
||||
// Hides agents enrolled in agentless policies by excluding the first 1000 agentless policy IDs
|
||||
// from the search. This limitation is to avoid hitting the `max_clause_count` limit.
|
||||
// In the future, we should hopefully be able to filter agentless agents using metadata:
|
||||
// https://github.com/elastic/elastic-agent/issues/7946
|
||||
if (showAgentless === false) {
|
||||
const agentlessPolicies = await agentPolicyService.list(soClient, {
|
||||
perPage: 1000,
|
||||
kuery: `${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}.supports_agentless:true`,
|
||||
});
|
||||
if (agentlessPolicies.items.length > 0) {
|
||||
filters.push(
|
||||
`NOT policy_id: (${agentlessPolicies.items.map((policy) => `"${policy.id}"`).join(' or ')})`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (showInactive === false) {
|
||||
filters.push(ACTIVE_AGENT_CONDITION);
|
||||
}
|
||||
|
||||
if (!includeUnenrolled(kuery)) {
|
||||
filters.push(ENROLLED_AGENT_CONDITION);
|
||||
}
|
||||
|
@ -386,6 +407,7 @@ export async function getAllAgentsByKuery(
|
|||
esClient: ElasticsearchClient,
|
||||
soClient: SavedObjectsClientContract,
|
||||
options: Omit<ListWithKuery, 'page' | 'perPage'> & {
|
||||
showAgentless?: boolean;
|
||||
showInactive: boolean;
|
||||
}
|
||||
): Promise<{
|
||||
|
|
|
@ -127,6 +127,7 @@ export async function reassignAgents(
|
|||
const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery;
|
||||
const res = await getAgentsByKuery(esClient, soClient, {
|
||||
kuery,
|
||||
showAgentless: options.showAgentless,
|
||||
showInactive: options.showInactive ?? false,
|
||||
page: 1,
|
||||
perPage: batchSize,
|
||||
|
|
|
@ -106,6 +106,7 @@ export async function unenrollAgents(
|
|||
const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery;
|
||||
const res = await getAgentsByKuery(esClient, soClient, {
|
||||
kuery,
|
||||
showAgentless: options.showAgentless,
|
||||
showInactive: options.showInactive ?? false,
|
||||
page: 1,
|
||||
perPage: batchSize,
|
||||
|
|
|
@ -22,9 +22,9 @@ export async function unenrollForAgentPolicyId(
|
|||
while (hasMore) {
|
||||
const { agents } = await getAgentsByKuery(esClient, soClient, {
|
||||
kuery: `${AGENTS_PREFIX}.policy_id:"${policyId}"`,
|
||||
showInactive: false,
|
||||
page: page++,
|
||||
perPage: 1000,
|
||||
showInactive: false,
|
||||
});
|
||||
|
||||
if (agents.length === 0) {
|
||||
|
|
|
@ -62,6 +62,7 @@ export async function updateAgentTags(
|
|||
// calculate total count
|
||||
const res = await getAgentsByKuery(esClient, soClient, {
|
||||
kuery,
|
||||
showAgentless: options.showAgentless,
|
||||
showInactive: options.showInactive ?? false,
|
||||
perPage: 0,
|
||||
pitId,
|
||||
|
|
|
@ -101,6 +101,7 @@ export async function sendUpgradeAgentsActions(
|
|||
|
||||
const res = await getAgentsByKuery(esClient, soClient, {
|
||||
kuery,
|
||||
showAgentless: options.showAgentless,
|
||||
showInactive: options.showInactive ?? false,
|
||||
page: 1,
|
||||
perPage: batchSize,
|
||||
|
|
|
@ -262,8 +262,13 @@ async function createSetupSideEffects(
|
|||
await ensureAgentPoliciesFleetServerKeysAndPolicies({ soClient, esClient, logger });
|
||||
stepSpan?.end();
|
||||
|
||||
logger.debug('Backfilling package policy supports_agentless field');
|
||||
await backfillPackagePolicySupportsAgentless(esClient);
|
||||
let backfillPackagePolicySupportsAgentlessError;
|
||||
try {
|
||||
logger.debug('Backfilling package policy supports_agentless field');
|
||||
await backfillPackagePolicySupportsAgentless(esClient);
|
||||
} catch (error) {
|
||||
backfillPackagePolicySupportsAgentlessError = { error };
|
||||
}
|
||||
|
||||
let ensureCorrectAgentlessSettingsIdsError;
|
||||
try {
|
||||
|
@ -286,6 +291,9 @@ async function createSetupSideEffects(
|
|||
const nonFatalErrors = [
|
||||
...preconfiguredPackagesNonFatalErrors,
|
||||
...(messageSigningServiceNonFatalError ? [messageSigningServiceNonFatalError] : []),
|
||||
...(backfillPackagePolicySupportsAgentlessError
|
||||
? [backfillPackagePolicySupportsAgentlessError]
|
||||
: []),
|
||||
...(ensureCorrectAgentlessSettingsIdsError ? [ensureCorrectAgentlessSettingsIdsError] : []),
|
||||
];
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ export const GetAgentsRequestSchema = {
|
|||
},
|
||||
})
|
||||
),
|
||||
showAgentless: schema.boolean({ defaultValue: true }),
|
||||
showInactive: schema.boolean({ defaultValue: false }),
|
||||
withMetrics: schema.boolean({ defaultValue: false }),
|
||||
showUpgradeable: schema.boolean({ defaultValue: false }),
|
||||
|
|
|
@ -47,6 +47,7 @@ export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContex
|
|||
async (context, request, response) => {
|
||||
let esAgents;
|
||||
const query = request.query as ListWithKuery & {
|
||||
showAgentless?: boolean;
|
||||
showInactive: boolean;
|
||||
searchAfter?: SortResults;
|
||||
pitId?: string;
|
||||
|
@ -110,6 +111,7 @@ export const getAgentsRoute = (router: IRouter, osqueryContext: OsqueryAppContex
|
|||
pitId: query.pitId,
|
||||
searchAfter: query.searchAfter,
|
||||
kuery,
|
||||
showAgentless: query.showAgentless,
|
||||
showInactive: query.showInactive,
|
||||
aggregations: {
|
||||
platforms: {
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { createServer } from '@mswjs/http-middleware';
|
||||
|
||||
import { http, HttpResponse, StrictResponse } from 'msw';
|
||||
|
||||
export const setupMockServer = () => {
|
||||
const server = createServer(...deploymentHandler);
|
||||
return server;
|
||||
};
|
||||
|
||||
interface AgentlessApiDeploymentResponse {
|
||||
code: string;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
const deploymentHandler = [
|
||||
http.post(
|
||||
'agentless-api/api/v1/ess/deployments',
|
||||
async ({ request }): Promise<StrictResponse<AgentlessApiDeploymentResponse>> => {
|
||||
return HttpResponse.json({
|
||||
code: 'SUCCESS',
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
),
|
||||
http.delete(
|
||||
'agentless-api/api/v1/ess/deployments/*',
|
||||
async (): Promise<StrictResponse<AgentlessApiDeploymentResponse>> => {
|
||||
return HttpResponse.json({
|
||||
code: 'SUCCESS',
|
||||
error: null,
|
||||
});
|
||||
}
|
||||
),
|
||||
];
|
|
@ -5,19 +5,25 @@
|
|||
* 2.0.
|
||||
*/
|
||||
|
||||
import * as http from 'http';
|
||||
import expect from '@kbn/expect';
|
||||
import { type Agent, FLEET_ELASTIC_AGENT_PACKAGE, AGENTS_INDEX } from '@kbn/fleet-plugin/common';
|
||||
|
||||
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
|
||||
import { setupMockServer } from './helpers/mock_agentless_api';
|
||||
|
||||
export default function ({ getService }: FtrProviderContext) {
|
||||
const esArchiver = getService('esArchiver');
|
||||
const supertest = getService('supertest');
|
||||
const es = getService('es');
|
||||
const mockAgentlessApiService = setupMockServer();
|
||||
let elasticAgentpkgVersion: string;
|
||||
|
||||
describe('fleet_list_agent', () => {
|
||||
let mockApiServer: http.Server;
|
||||
|
||||
before(async () => {
|
||||
mockApiServer = await mockAgentlessApiService.listen(8089); // Start the agentless api mock server on port 8089
|
||||
await esArchiver.loadIfNeeded('x-pack/test/functional/es_archives/fleet/agents');
|
||||
const getPkRes = await supertest
|
||||
.get(`/api/fleet/epm/packages/${FLEET_ELASTIC_AGENT_PACKAGE}`)
|
||||
|
@ -34,6 +40,7 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
.expect(200);
|
||||
});
|
||||
after(async () => {
|
||||
mockApiServer.close();
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents');
|
||||
await supertest
|
||||
.delete(`/api/fleet/epm/packages/${FLEET_ELASTIC_AGENT_PACKAGE}/${elasticAgentpkgVersion}`)
|
||||
|
@ -267,6 +274,73 @@ export default function ({ getService }: FtrProviderContext) {
|
|||
});
|
||||
});
|
||||
|
||||
it('should not return agentless agents when showAgentless=false', async () => {
|
||||
// Set up default Fleet Server host, needed during agentless agent creation
|
||||
await supertest
|
||||
.post(`/api/fleet/fleet_server_hosts`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
id: 'default-agentless-fleet-server-host',
|
||||
name: 'Default',
|
||||
is_default: true,
|
||||
host_urls: ['https://test.com:8080', 'https://test.com:8081'],
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
// Create an agentless agent policy
|
||||
const { body: policyRes } = await supertest
|
||||
.post('/api/fleet/agent_policies')
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({
|
||||
name: 'Agentless Policy',
|
||||
namespace: 'default',
|
||||
supports_agentless: true,
|
||||
})
|
||||
.expect(200);
|
||||
|
||||
const agentlessPolicyId = policyRes.item.id;
|
||||
|
||||
// Enroll an agent into the agentless policy
|
||||
const agentId = 'agentless-agent-1';
|
||||
await es.index({
|
||||
index: AGENTS_INDEX,
|
||||
id: agentId,
|
||||
refresh: 'wait_for',
|
||||
document: {
|
||||
type: 'PERMANENT',
|
||||
active: true,
|
||||
enrolled_at: new Date().toISOString(),
|
||||
local_metadata: { elastic: { agent: { id: agentId, version: '9.0.0' } } },
|
||||
status: 'online',
|
||||
policy_id: agentlessPolicyId,
|
||||
},
|
||||
});
|
||||
|
||||
// Call the agent list API without showAgentless parameter
|
||||
const { body: apiResponseWithAgentless } = await supertest.get('/api/fleet/agents');
|
||||
|
||||
// Assert that the agentless agent is returned
|
||||
const agentIdsWithAgentless = apiResponseWithAgentless.items.map((agent: any) => agent.id);
|
||||
expect(agentIdsWithAgentless).to.contain(agentId);
|
||||
|
||||
// Call the agent list API with showAgentless=false
|
||||
const { body: apiResponse } = await supertest
|
||||
.get('/api/fleet/agents?showAgentless=false')
|
||||
.expect(200);
|
||||
|
||||
// Assert that the agentless agent is not returned
|
||||
const agentIds = apiResponse.items.map((agent: any) => agent.id);
|
||||
expect(agentIds).not.contain(agentId);
|
||||
|
||||
// Cleanup: delete the agent and policy
|
||||
await es.delete({ index: AGENTS_INDEX, id: agentId, refresh: 'wait_for' });
|
||||
await supertest
|
||||
.post(`/api/fleet/agent_policies/delete`)
|
||||
.set('kbn-xsrf', 'xxxx')
|
||||
.send({ agentPolicyId: agentlessPolicyId })
|
||||
.expect(200);
|
||||
});
|
||||
|
||||
describe('advanced search params', () => {
|
||||
afterEach(async () => {
|
||||
await esArchiver.unload('x-pack/test/functional/es_archives/fleet/agents');
|
||||
|
|
|
@ -14,6 +14,7 @@ import {
|
|||
getKibanaCliLoggers,
|
||||
} from '@kbn/test';
|
||||
import { ScoutTestRunConfigCategory } from '@kbn/scout-info';
|
||||
import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils';
|
||||
|
||||
const getFullPath = (relativePath: string) => path.join(path.dirname(__filename), relativePath);
|
||||
|
||||
|
@ -91,10 +92,10 @@ export default async function ({ readConfigFile, log }: FtrConfigProviderContext
|
|||
])}`,
|
||||
`--xpack.cloud.id='123456789'`,
|
||||
`--xpack.fleet.agentless.enabled=true`,
|
||||
`--xpack.fleet.agentless.api.url=https://api.agentless.url/api/v1/ess`,
|
||||
`--xpack.fleet.agentless.api.tls.certificate=./config/node.crt`,
|
||||
`--xpack.fleet.agentless.api.tls.key=./config/node.key`,
|
||||
`--xpack.fleet.agentless.api.tls.ca=./config/ca.crt`,
|
||||
`--xpack.fleet.agentless.api.url=http://localhost:8089/agentless-api`,
|
||||
`--xpack.fleet.agentless.api.tls.certificate=${KBN_CERT_PATH}`,
|
||||
`--xpack.fleet.agentless.api.tls.key=${KBN_KEY_PATH}`,
|
||||
`--xpack.fleet.agentless.api.tls.ca=${CA_CERT_PATH}`,
|
||||
`--xpack.fleet.internal.registry.kibanaVersionCheckEnabled=false`,
|
||||
`--xpack.fleet.internal.registry.spec.min=1.0`,
|
||||
`--logging.loggers=${JSON.stringify([
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue