[Fleet] Remove fleetServerEnabled feature flag and use fleet system indices (#92422)

This commit is contained in:
Nicolas Chaulet 2021-03-08 07:40:09 -05:00 committed by GitHub
parent 96c360b81d
commit f4caf8727d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
83 changed files with 2249 additions and 1720 deletions

View file

@ -1678,7 +1678,32 @@
}
},
"server": {
"classes": [],
"classes": [
{
"id": "def-server.AgentNotFoundError",
"type": "Class",
"tags": [],
"label": "AgentNotFoundError",
"description": [],
"signature": [
{
"pluginId": "fleet",
"scope": "server",
"docId": "kibFleetPluginApi",
"section": "def-server.AgentNotFoundError",
"text": "AgentNotFoundError"
},
" extends ",
"IngestManagerError"
],
"children": [],
"source": {
"path": "x-pack/plugins/fleet/server/errors/index.ts",
"lineNumber": 34
},
"initialIsOpen": false
}
],
"functions": [
{
"id": "def-server.getRegistryUrl",
@ -1714,7 +1739,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 73
"lineNumber": 68
},
"signature": [
"(soClient: Pick<",
@ -1744,7 +1769,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 74
"lineNumber": 69
},
"signature": [
"(soClient: Pick<",
@ -1774,7 +1799,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 75
"lineNumber": 70
},
"signature": [
"(soClient: Pick<",
@ -1796,7 +1821,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 76
"lineNumber": 71
},
"signature": [
"(soClient: Pick<",
@ -1821,7 +1846,7 @@
],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 72
"lineNumber": 67
},
"initialIsOpen": false
},
@ -1862,15 +1887,7 @@
"type": "Function",
"label": "authenticateAgentWithAccessToken",
"signature": [
"(soClient: Pick<",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCoreSavedObjectsPluginApi",
"section": "def-server.SavedObjectsClient",
"text": "SavedObjectsClient"
},
", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, esClient: ",
"(esClient: ",
{
"pluginId": "core",
"scope": "server",
@ -1900,27 +1917,6 @@
"\nAuthenticate an agent with access toekn"
],
"children": [
{
"type": "Object",
"label": "soClient",
"isRequired": true,
"signature": [
"Pick<",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCoreSavedObjectsPluginApi",
"section": "def-server.SavedObjectsClient",
"text": "SavedObjectsClient"
},
", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">"
],
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 54
}
},
{
"type": "CompoundType",
"label": "esClient",
@ -1937,7 +1933,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 55
"lineNumber": 54
}
},
{
@ -1957,7 +1953,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 56
"lineNumber": 55
}
}
],
@ -1973,15 +1969,7 @@
"type": "Function",
"label": "getAgentStatusById",
"signature": [
"(soClient: Pick<",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCoreSavedObjectsPluginApi",
"section": "def-server.SavedObjectsClient",
"text": "SavedObjectsClient"
},
", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">, esClient: ",
"(esClient: ",
{
"pluginId": "core",
"scope": "server",
@ -2003,27 +1991,6 @@
"\nReturn the status by the Agent's id"
],
"children": [
{
"type": "Object",
"label": "soClient",
"isRequired": true,
"signature": [
"Pick<",
{
"pluginId": "core",
"scope": "server",
"docId": "kibCoreSavedObjectsPluginApi",
"section": "def-server.SavedObjectsClient",
"text": "SavedObjectsClient"
},
", \"get\" | \"delete\" | \"create\" | \"find\" | \"update\" | \"bulkCreate\" | \"bulkGet\" | \"bulkUpdate\" | \"errors\" | \"checkConflicts\" | \"resolve\" | \"addToNamespaces\" | \"deleteFromNamespaces\" | \"removeReferencesTo\" | \"openPointInTimeForType\" | \"closePointInTime\">"
],
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 62
}
},
{
"type": "CompoundType",
"label": "esClient",
@ -2040,7 +2007,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 63
"lineNumber": 60
}
},
{
@ -2053,7 +2020,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 64
"lineNumber": 60
}
}
],
@ -2061,7 +2028,7 @@
"returnComment": [],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 61
"lineNumber": 60
}
},
{
@ -2074,7 +2041,7 @@
],
"source": {
"path": "x-pack/plugins/fleet/server/services/index.ts",
"lineNumber": 69
"lineNumber": 64
},
"signature": [
"typeof ",
@ -2471,7 +2438,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/server/index.ts",
"lineNumber": 75
"lineNumber": 76
},
"signature": [
"any"
@ -2933,7 +2900,7 @@
"description": [],
"source": {
"path": "x-pack/plugins/fleet/common/types/index.ts",
"lineNumber": 39
"lineNumber": 38
},
"signature": [
"<T>(o: T) => [keyof T, T[keyof T]][]"
@ -5188,7 +5155,7 @@
"lineNumber": 15
},
"signature": [
"{ fleetServerEnabled: boolean; enabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; maxConcurrentConnections: number; kibana: { host?: string | string[] | undefined; ca_sha256?: string | undefined; }; elasticsearch: { host?: string | undefined; ca_sha256?: string | undefined; }; agentPolicyRolloutRateLimitIntervalMs: number; agentPolicyRolloutRateLimitRequestPerInterval: number; }"
"{ enabled: boolean; tlsCheckDisabled: boolean; pollingRequestTimeout: number; maxConcurrentConnections: number; kibana: { host?: string | string[] | undefined; ca_sha256?: string | undefined; }; elasticsearch: { host?: string | undefined; ca_sha256?: string | undefined; }; agentPolicyRolloutRateLimitIntervalMs: number; agentPolicyRolloutRateLimitRequestPerInterval: number; }"
]
}
],
@ -13693,7 +13660,7 @@
],
"source": {
"path": "x-pack/plugins/fleet/common/types/index.ts",
"lineNumber": 44
"lineNumber": 43
},
"signature": [
"T[keyof T]"

View file

@ -45,6 +45,9 @@ import fleetObj from './fleet.json';
### Functions
<DocDefinitionList data={fleetObj.server.functions}/>
### Classes
<DocDefinitionList data={fleetObj.server.classes}/>
### Interfaces
<DocDefinitionList data={fleetObj.server.interfaces}/>

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import { AGENT_POLLING_THRESHOLD_MS, AGENT_SAVED_OBJECT_TYPE } from '../constants';
import { AGENT_POLLING_THRESHOLD_MS } from '../constants';
import type { Agent, AgentStatus } from '../types';
export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentStatus {
@ -42,11 +42,11 @@ export function getAgentStatus(agent: Agent, now: number = Date.now()): AgentSta
}
export function buildKueryForEnrollingAgents() {
return `not (${AGENT_SAVED_OBJECT_TYPE}.last_checkin:*)`;
return 'not (last_checkin:*)';
}
export function buildKueryForUnenrollingAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.unenrollment_started_at:*`;
return 'unenrollment_started_at:*';
}
export function buildKueryForOnlineAgents() {
@ -54,17 +54,17 @@ export function buildKueryForOnlineAgents() {
}
export function buildKueryForErrorAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:error or ${AGENT_SAVED_OBJECT_TYPE}.last_checkin_status:degraded`;
return 'last_checkin_status:error or .last_checkin_status:degraded';
}
export function buildKueryForOfflineAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.last_checkin < now-${
return `last_checkin < now-${
(4 * AGENT_POLLING_THRESHOLD_MS) / 1000
}s AND not (${buildKueryForErrorAgents()}) AND not ( ${buildKueryForUpdatingAgents()} )`;
}
export function buildKueryForUpgradingAgents() {
return `(${AGENT_SAVED_OBJECT_TYPE}.upgrade_started_at:*) and not (${AGENT_SAVED_OBJECT_TYPE}.upgraded_at:*)`;
return '(upgrade_started_at:*) and not (upgraded_at:*)';
}
export function buildKueryForUpdatingAgents() {
@ -72,5 +72,5 @@ export function buildKueryForUpdatingAgents() {
}
export function buildKueryForInactiveAgents() {
return `${AGENT_SAVED_OBJECT_TYPE}.active:false`;
return `active:false`;
}

View file

@ -13,7 +13,6 @@ export interface FleetConfigType {
registryUrl?: string;
registryProxyUrl?: string;
agents: {
fleetServerEnabled: boolean;
enabled: boolean;
tlsCheckDisabled: boolean;
pollingRequestTimeout: number;

View file

@ -14,7 +14,6 @@ export const createConfigurationMock = (): FleetConfigType => {
registryProxyUrl: '',
agents: {
enabled: true,
fleetServerEnabled: false,
tlsCheckDisabled: true,
pollingRequestTimeout: 1000,
maxConcurrentConnections: 100,

View file

@ -19,8 +19,7 @@ import { FormattedMessage } from '@kbn/i18n/react';
import { AgentPolicy } from '../../../../types';
import { SearchBar } from '../../../../components';
import { AGENTS_INDEX, AGENT_SAVED_OBJECT_TYPE } from '../../../../constants';
import { useConfig } from '../../../../hooks';
import { AGENTS_INDEX } from '../../../../constants';
const statusFilters = [
{
@ -78,7 +77,6 @@ export const SearchAndFilterBar: React.FunctionComponent<{
showUpgradeable,
onShowUpgradeableChange,
}) => {
const config = useConfig();
// Policies state for filtering
const [isAgentPoliciesFilterOpen, setIsAgentPoliciesFilterOpen] = useState<boolean>(false);
@ -112,13 +110,7 @@ export const SearchAndFilterBar: React.FunctionComponent<{
onSubmitSearch(newSearch);
}
}}
{...(config.agents.fleetServerEnabled
? {
indexPattern: AGENTS_INDEX,
}
: {
fieldPrefix: AGENT_SAVED_OBJECT_TYPE,
})}
indexPattern={AGENTS_INDEX}
/>
</EuiFlexItem>
<EuiFlexItem grow={2}>

View file

@ -21,10 +21,7 @@ import {
} from '@elastic/eui';
import { FormattedMessage, FormattedDate } from '@kbn/i18n/react';
import {
ENROLLMENT_API_KEYS_INDEX,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
} from '../../../constants';
import { ENROLLMENT_API_KEYS_INDEX } from '../../../constants';
import {
useBreadcrumbs,
usePagination,
@ -33,7 +30,6 @@ import {
sendGetOneEnrollmentAPIKey,
useStartServices,
sendDeleteOneEnrollmentAPIKey,
useConfig,
} from '../../../hooks';
import { EnrollmentAPIKey } from '../../../types';
import { SearchBar } from '../../../components/search_bar';
@ -160,7 +156,6 @@ const DeleteButton: React.FunctionComponent<{ apiKey: EnrollmentAPIKey; refresh:
export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => {
useBreadcrumbs('fleet_enrollment_tokens');
const config = useConfig();
const [flyoutOpen, setFlyoutOpen] = useState(false);
const [search, setSearch] = useState('');
const { pagination, setPagination, pageSizeOptions } = usePagination();
@ -288,13 +283,7 @@ export const EnrollmentTokenListPage: React.FunctionComponent<{}> = () => {
});
setSearch(newSearch);
}}
{...(config.agents.fleetServerEnabled
? {
indexPattern: ENROLLMENT_API_KEYS_INDEX,
}
: {
fieldPrefix: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
})}
indexPattern={ENROLLMENT_API_KEYS_INDEX}
/>
</EuiFlexItem>
<EuiFlexItem grow={false}>

View file

@ -25,8 +25,7 @@ export const getAgentUsage = async (
esClient?: ElasticsearchClient
): Promise<AgentUsage> => {
// TODO: unsure if this case is possible at all.
const fleetServerMissing = config.agents.fleetServerEnabled && !(await isFleetServerSetup());
if (!soClient || !esClient || fleetServerMissing) {
if (!soClient || !esClient || !(await isFleetServerSetup())) {
return {
total: 0,
online: 0,

View file

@ -24,6 +24,7 @@ import {
AgentPolicyNameExistsError,
PackageUnsupportedMediaTypeError,
ConcurrentInstallOperationError,
AgentNotFoundError,
} from './index';
type IngestErrorHandler = (
@ -78,6 +79,9 @@ const getHTTPResponseCode = (error: IngestManagerError): number => {
if (error instanceof ConcurrentInstallOperationError) {
return 409; // Conflict
}
if (error instanceof AgentNotFoundError) {
return 404;
}
return 400; // Bad Request
};

View file

@ -19,6 +19,7 @@ export class IngestManagerError extends Error {
this.name = this.constructor.name; // for stack traces
}
}
export class RegistryError extends IngestManagerError {}
export class RegistryConnectionError extends RegistryError {}
export class RegistryResponseError extends RegistryError {
@ -30,6 +31,7 @@ export class PackageNotFoundError extends IngestManagerError {}
export class PackageKeyInvalidError extends IngestManagerError {}
export class PackageOutdatedError extends IngestManagerError {}
export class AgentPolicyError extends IngestManagerError {}
export class AgentNotFoundError extends IngestManagerError {}
export class AgentPolicyNameExistsError extends AgentPolicyError {}
export class PackageUnsupportedMediaTypeError extends IngestManagerError {}
export class PackageInvalidArchiveError extends IngestManagerError {}

View file

@ -26,15 +26,17 @@ export {
AgentPolicyServiceInterface,
} from './services';
export { FleetSetupContract, FleetSetupDeps, FleetStartContract, ExternalCallback } from './plugin';
export { AgentNotFoundError } from './errors';
export const config: PluginConfigDescriptor = {
exposeToBrowser: {
epm: true,
agents: true,
},
deprecations: ({ renameFromRoot }) => [
deprecations: ({ renameFromRoot, unused }) => [
renameFromRoot('xpack.ingestManager', 'xpack.fleet'),
renameFromRoot('xpack.fleet.fleet', 'xpack.fleet.agents'),
unused('agents.fleetServerEnabled'),
],
schema: schema.object({
enabled: schema.boolean({ defaultValue: true }),
@ -42,7 +44,6 @@ export const config: PluginConfigDescriptor = {
registryProxyUrl: schema.maybe(schema.uri({ scheme: ['http', 'https'] })),
agents: schema.object({
enabled: schema.boolean({ defaultValue: true }),
fleetServerEnabled: schema.boolean({ defaultValue: false }),
tlsCheckDisabled: schema.boolean({ defaultValue: false }),
pollingRequestTimeout: schema.number({
defaultValue: AGENT_POLLING_REQUEST_TIMEOUT_MS,

View file

@ -299,10 +299,7 @@ export class FleetPlugin
licenseService.start(this.licensing$);
agentCheckinState.start();
if (appContextService.getConfig()?.agents?.fleetServerEnabled) {
// Break the promise chain, the error handling is done in startFleetServerSetup
startFleetServerSetup();
}
startFleetServerSetup();
return {
esIndexPatternService: new ESIndexPatternSavedObjectService(),

View file

@ -21,7 +21,7 @@ export const postAgentAcksHandlerBuilder = function (
try {
const soClient = ackService.getSavedObjectsClientContract(request);
const esClient = ackService.getElasticsearchClientContract();
const agent = await ackService.authenticateAgentWithAccessToken(soClient, esClient, request);
const agent = await ackService.authenticateAgentWithAccessToken(esClient, request);
const agentEvents = request.body.events as AgentEvent[];
// validate that all events are for the authorized agent obtained from the api key

View file

@ -27,7 +27,7 @@ export const postNewAgentActionHandlerBuilder = function (
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asInternalUser;
const agent = await actionsService.getAgent(soClient, esClient, request.params.agentId);
const agent = await actionsService.getAgent(esClient, request.params.agentId);
const newAgentAction = request.body.action;

View file

@ -44,7 +44,7 @@ export const getAgentHandler: RequestHandler<
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
const agent = await AgentService.getAgent(soClient, esClient, request.params.agentId);
const agent = await AgentService.getAgent(esClient, request.params.agentId);
const body: GetOneAgentResponse = {
item: {
@ -101,11 +101,10 @@ export const getAgentEventsHandler: RequestHandler<
export const deleteAgentHandler: RequestHandler<
TypeOf<typeof DeleteAgentRequestSchema.params>
> = async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
await AgentService.deleteAgent(soClient, esClient, request.params.agentId);
await AgentService.deleteAgent(esClient, request.params.agentId);
const body = {
action: 'deleted',
@ -129,14 +128,13 @@ export const updateAgentHandler: RequestHandler<
undefined,
TypeOf<typeof UpdateAgentRequestSchema.body>
> = async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
await AgentService.updateAgent(soClient, esClient, request.params.agentId, {
await AgentService.updateAgent(esClient, request.params.agentId, {
user_provided_metadata: request.body.user_provided_metadata,
});
const agent = await AgentService.getAgent(soClient, esClient, request.params.agentId);
const agent = await AgentService.getAgent(esClient, request.params.agentId);
const body = {
item: {
@ -165,7 +163,7 @@ export const postAgentCheckinHandler: RequestHandler<
try {
const soClient = appContextService.getInternalUserSOClient(request);
const esClient = appContextService.getInternalUserESClient();
const agent = await AgentService.authenticateAgentWithAccessToken(soClient, esClient, request);
const agent = await AgentService.authenticateAgentWithAccessToken(esClient, request);
const abortController = new AbortController();
request.events.aborted$.subscribe(() => {
abortController.abort();
@ -209,11 +207,7 @@ export const postAgentEnrollHandler: RequestHandler<
const soClient = appContextService.getInternalUserSOClient(request);
const esClient = context.core.elasticsearch.client.asInternalUser;
const { apiKeyId } = APIKeyService.parseApiKeyFromHeaders(request.headers);
const enrollmentAPIKey = await APIKeyService.getEnrollmentAPIKeyById(
soClient,
esClient,
apiKeyId
);
const enrollmentAPIKey = await APIKeyService.getEnrollmentAPIKeyById(esClient, apiKeyId);
if (!enrollmentAPIKey || !enrollmentAPIKey.active) {
return response.unauthorized({
@ -248,11 +242,10 @@ export const getAgentsHandler: RequestHandler<
undefined,
TypeOf<typeof GetAgentsRequestSchema.query>
> = async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
const { agents, total, page, perPage } = await AgentService.listAgents(soClient, esClient, {
const { agents, total, page, perPage } = await AgentService.listAgents(esClient, {
page: request.query.page,
perPage: request.query.perPage,
showInactive: request.query.showInactive,
@ -260,7 +253,7 @@ export const getAgentsHandler: RequestHandler<
kuery: request.query.kuery,
});
const totalInactive = request.query.showInactive
? await AgentService.countInactiveAgents(soClient, esClient, {
? await AgentService.countInactiveAgents(esClient, {
kuery: request.query.kuery,
})
: 0;

View file

@ -36,7 +36,7 @@ export const postAgentUpgradeHandler: RequestHandler<
},
});
}
const agent = await getAgent(soClient, esClient, request.params.agentId);
const agent = await getAgent(esClient, request.params.agentId);
if (agent.unenrollment_started_at || agent.unenrolled_at) {
return response.customError({
statusCode: 400,

View file

@ -58,7 +58,7 @@ export const getAgentPoliciesHandler: RequestHandler<
await bluebird.map(
items,
(agentPolicy: GetAgentPoliciesResponseItem) =>
listAgents(soClient, esClient, {
listAgents(esClient, {
showInactive: false,
perPage: 0,
page: 1,

View file

@ -27,19 +27,14 @@ export const getEnrollmentApiKeysHandler: RequestHandler<
undefined,
TypeOf<typeof GetEnrollmentAPIKeysRequestSchema.query>
> = async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(
soClient,
esClient,
{
page: request.query.page,
perPage: request.query.perPage,
kuery: request.query.kuery,
}
);
const { items, total, page, perPage } = await APIKeyService.listEnrollmentApiKeys(esClient, {
page: request.query.page,
perPage: request.query.perPage,
kuery: request.query.kuery,
});
const body: GetEnrollmentAPIKeysResponse = { list: items, total, page, perPage };
return response.ok({ body });
@ -93,14 +88,9 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler<
export const getOneEnrollmentApiKeyHandler: RequestHandler<
TypeOf<typeof GetOneEnrollmentAPIKeyRequestSchema.params>
> = async (context, request, response) => {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
try {
const apiKey = await APIKeyService.getEnrollmentAPIKey(
soClient,
esClient,
request.params.keyId
);
const apiKey = await APIKeyService.getEnrollmentAPIKey(esClient, request.params.keyId);
const body: GetOneEnrollmentAPIKeyResponse = { item: apiKey };
return response.ok({ body });

View file

@ -520,7 +520,7 @@ class AgentPolicyService {
throw new Error('The default agent policy cannot be deleted');
}
const { total } = await listAgents(soClient, esClient, {
const { total } = await listAgents(esClient, {
showInactive: false,
perPage: 0,
page: 1,
@ -554,9 +554,8 @@ class AgentPolicyService {
agentPolicyId: string
) {
const esClient = appContextService.getInternalUserESClient();
if (appContextService.getConfig()?.agents?.fleetServerEnabled) {
await this.createFleetPolicyChangeFleetServer(soClient, esClient, agentPolicyId);
}
await this.createFleetPolicyChangeFleetServer(soClient, esClient, agentPolicyId);
return this.createFleetPolicyChangeActionSO(soClient, esClient, agentPolicyId);
}

View file

@ -56,6 +56,6 @@ export async function agentPolicyUpdateEventHandler(
if (action === 'deleted') {
await unenrollForAgentPolicyId(soClient, esClient, agentPolicyId);
await deleteEnrollmentApiKeyForAgentPolicyId(soClient, agentPolicyId);
await deleteEnrollmentApiKeyForAgentPolicyId(soClient, esClient, agentPolicyId);
}
}

View file

@ -108,16 +108,21 @@ describe('test agent acks services', () => {
]
);
expect(mockSavedObjectsClient.bulkUpdate).not.toBeCalled();
expect(mockSavedObjectsClient.update).toBeCalled();
expect(mockSavedObjectsClient.update.mock.calls[0]).toMatchInlineSnapshot(`
expect(mockElasticsearchClient.update).toBeCalled();
expect(mockElasticsearchClient.update.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"fleet-agents",
"id",
Object {
"packages": Array [
"system",
],
"policy_revision": 4,
"body": Object {
"doc": Object {
"packages": Array [
"system",
],
"policy_revision_idx": 4,
},
},
"id": "id",
"index": ".fleet-agents",
"refresh": "wait_for",
},
]
`);
@ -170,16 +175,21 @@ describe('test agent acks services', () => {
]
);
expect(mockSavedObjectsClient.bulkUpdate).not.toBeCalled();
expect(mockSavedObjectsClient.update).toBeCalled();
expect(mockSavedObjectsClient.update.mock.calls[0]).toMatchInlineSnapshot(`
expect(mockElasticsearchClient.update).toBeCalled();
expect(mockElasticsearchClient.update.mock.calls[0]).toMatchInlineSnapshot(`
Array [
"fleet-agents",
"id",
Object {
"packages": Array [
"system",
],
"policy_revision": 4,
"body": Object {
"doc": Object {
"packages": Array [
"system",
],
"policy_revision_idx": 4,
},
},
"id": "id",
"index": ".fleet-agents",
"refresh": "wait_for",
},
]
`);

View file

@ -92,7 +92,7 @@ export async function acknowledgeAgentActions(
const configChangeAction = getLatestConfigChangePolicyActionIfUpdated(agent, actions);
if (configChangeAction) {
await updateAgent(soClient, esClient, agent.id, {
await updateAgent(esClient, agent.id, {
policy_revision: configChangeAction.policy_revision,
packages: configChangeAction?.ack_data?.packages,
});
@ -201,7 +201,6 @@ export interface AcksService {
) => Promise<AgentAction[]>;
authenticateAgentWithAccessToken: (
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
request: KibanaRequest
) => Promise<Agent>;

View file

@ -76,10 +76,7 @@ async function createAction(
}
);
if (
appContextService.getConfig()?.agents?.fleetServerEnabled &&
isAgentActionSavedObject(actionSO)
) {
if (isAgentActionSavedObject(actionSO)) {
const body: FleetServerAgentAction = {
'@timestamp': new Date().toISOString(),
expiration: new Date(Date.now() + ONE_MONTH_IN_MS).toISOString(),
@ -140,7 +137,7 @@ async function bulkCreateActions(
}))
);
if (appContextService.getConfig()?.agents?.fleetServerEnabled) {
if (actionSOs.length > 0) {
await esClient.bulk({
index: AGENT_ACTIONS_INDEX,
body: actionSOs.flatMap((actionSO) => {
@ -371,11 +368,7 @@ export async function getLatestConfigChangeAction(
}
export interface ActionsService {
getAgent: (
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string
) => Promise<Agent>;
getAgent: (esClient: ElasticsearchClient, agentId: string) => Promise<Agent>;
createAgentAction: (
soClient: SavedObjectsClientContract,

View file

@ -6,35 +6,33 @@
*/
import { KibanaRequest } from 'kibana/server';
import { savedObjectsClientMock, elasticsearchServiceMock } from 'src/core/server/mocks';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { authenticateAgentWithAccessToken } from './authenticate';
const mockEsClient = elasticsearchServiceMock.createInternalClient();
describe('test agent autenticate services', () => {
it('should succeed with a valid API key and an active agent', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
const mockEsClient = elasticsearchServiceMock.createInternalClient();
mockEsClient.search.mockResolvedValue({
body: {
hits: {
hits: [
{
// @ts-expect-error
_id: 'agent1',
_source: {
// @ts-expect-error
active: true,
// @ts-expect-error
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
},
],
})
);
await authenticateAgentWithAccessToken(mockSavedObjectsClient, mockEsClient, {
],
},
},
});
await authenticateAgentWithAccessToken(mockEsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
@ -43,28 +41,28 @@ describe('test agent autenticate services', () => {
});
it('should throw if the request is not authenticated', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
const mockEsClient = elasticsearchServiceMock.createInternalClient();
mockEsClient.search.mockResolvedValue({
body: {
hits: {
hits: [
{
// @ts-expect-error
_id: 'agent1',
_source: {
// @ts-expect-error
active: true,
// @ts-expect-error
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
},
],
})
);
],
},
},
});
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, mockEsClient, {
authenticateAgentWithAccessToken(mockEsClient, {
auth: { isAuthenticated: false },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
@ -74,28 +72,29 @@ describe('test agent autenticate services', () => {
});
it('should throw if the ApiKey headers is malformed', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: false,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
})
);
const mockEsClient = elasticsearchServiceMock.createInternalClient();
const hits = [
{
_id: 'agent1',
_source: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
];
mockEsClient.search.mockResolvedValue({
body: {
hits: {
// @ts-expect-error
hits,
},
},
});
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, mockEsClient, {
authenticateAgentWithAccessToken(mockEsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'aaaa',
@ -105,28 +104,27 @@ describe('test agent autenticate services', () => {
});
it('should throw if the agent is not active', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: false,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
})
);
const mockEsClient = elasticsearchServiceMock.createInternalClient();
const hits = [
{
_id: 'agent1',
_source: {
active: false,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
];
mockEsClient.search.mockResolvedValue({
body: {
hits: {
// @ts-expect-error
hits,
},
},
});
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, mockEsClient, {
authenticateAgentWithAccessToken(mockEsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',
@ -136,17 +134,18 @@ describe('test agent autenticate services', () => {
});
it('should throw if there is no agent matching the API key', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
mockSavedObjectsClient.find.mockReturnValue(
Promise.resolve({
page: 1,
per_page: 100,
total: 1,
saved_objects: [],
})
);
const mockEsClient = elasticsearchServiceMock.createInternalClient();
mockEsClient.search.mockResolvedValue({
body: {
hits: {
// @ts-expect-error
hits: [],
},
},
});
expect(
authenticateAgentWithAccessToken(mockSavedObjectsClient, mockEsClient, {
authenticateAgentWithAccessToken(mockEsClient, {
auth: { isAuthenticated: true },
headers: {
authorization: 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==',

View file

@ -7,7 +7,7 @@
import Boom from '@hapi/boom';
import { KibanaRequest } from 'src/core/server';
import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
import type { ElasticsearchClient } from 'src/core/server';
import type { Agent } from '../../types';
import * as APIKeyService from '../api_keys';
@ -15,7 +15,6 @@ import * as APIKeyService from '../api_keys';
import { getAgentByAccessAPIKeyId } from './crud';
export async function authenticateAgentWithAccessToken(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
request: KibanaRequest
): Promise<Agent> {
@ -29,7 +28,7 @@ export async function authenticateAgentWithAccessToken(
throw Boom.unauthorized(err.message);
}
const agent = await getAgentByAccessAPIKeyId(soClient, esClient, res.apiKeyId);
const agent = await getAgentByAccessAPIKeyId(esClient, res.apiKeyId);
return agent;
}

View file

@ -46,7 +46,7 @@ export async function agentCheckin(
}
// Update agent only if something changed
if (Object.keys(updateData).length > 0) {
await updateAgent(soClient, esClient, agent.id, updateData);
await updateAgent(esClient, agent.id, updateData);
}
// Check if some actions are not acknowledged
let actions = await getAgentActionsForCheckin(soClient, agent.id);

View file

@ -5,29 +5,9 @@
* 2.0.
*/
import { KibanaRequest } from 'src/core/server';
import { appContextService } from '../../app_context';
import { bulkUpdateAgents } from '../crud';
function getInternalUserSOClient() {
const fakeRequest = ({
headers: {},
getBasePath: () => '',
path: '/',
route: { settings: {} },
url: {
href: '/',
},
raw: {
req: {
url: '/',
},
},
} as unknown) as KibanaRequest;
return appContextService.getInternalUserSOClient(fakeRequest);
}
export function agentCheckinStateConnectedAgentsFactory() {
const connectedAgentsIds = new Set<string>();
let agentToUpdate = new Set<string>();
@ -58,7 +38,6 @@ export function agentCheckinStateConnectedAgentsFactory() {
return;
}
const esClient = appContextService.getInternalUserESClient();
const internalSOClient = getInternalUserSOClient();
const now = new Date().toISOString();
const updates = [...agentToUpdate.values()].map((agentId) => ({
agentId,
@ -67,7 +46,7 @@ export function agentCheckinStateConnectedAgentsFactory() {
},
}));
agentToUpdate = new Set<string>([...connectedAgentsIds.values()]);
await bulkUpdateAgents(internalSOClient, esClient, updates);
await bulkUpdateAgents(esClient, updates);
}
return {

View file

@ -124,6 +124,7 @@ describe('test agent checkin new action services', () => {
current_error_events: [],
packages: [],
enrolled_at: '2020-03-14T19:45:02.620Z',
default_api_key: 'MOCK_API_KEY',
};
const mockPolicyAction: AgentPolicyAction = {
id: 'action1',

View file

@ -24,16 +24,9 @@ import {
import { KibanaRequest } from 'src/core/server';
import type { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
import type {
Agent,
AgentAction,
AgentPolicyAction,
AgentPolicyActionV7_9,
AgentSOAttributes,
} from '../../../types';
import type { Agent, AgentAction, AgentPolicyAction, AgentPolicyActionV7_9 } from '../../../types';
import * as APIKeysService from '../../api_keys';
import {
AGENT_SAVED_OBJECT_TYPE,
AGENT_UPDATE_ACTIONS_INTERVAL_MS,
AGENT_POLLING_REQUEST_TIMEOUT_MARGIN_MS,
AGENT_POLICY_ROLLOUT_RATE_LIMIT_INTERVAL_MS,
@ -113,17 +106,7 @@ async function getAgentDefaultOutputAPIKey(
esClient: ElasticsearchClient,
agent: Agent
) {
if (appContextService.getConfig()?.agents?.fleetServerEnabled) {
return agent.default_api_key;
} else {
const {
attributes: { default_api_key: defaultApiKey },
} = await appContextService
.getEncryptedSavedObjects()
.getDecryptedAsInternalUser<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agent.id);
return defaultApiKey;
}
return agent.default_api_key;
}
async function getOrCreateAgentDefaultOutputAPIKey(
@ -137,7 +120,7 @@ async function getOrCreateAgentDefaultOutputAPIKey(
}
const outputAPIKey = await APIKeysService.generateOutputApiKey(soClient, 'default', agent.id);
await updateAgent(soClient, esClient, agent.id, {
await updateAgent(esClient, agent.id, {
default_api_key: outputAPIKey.key,
default_api_key_id: outputAPIKey.id,
});
@ -282,7 +265,7 @@ export function agentCheckinStateNewActionsFactory() {
(action) => action.type === 'INTERNAL_POLICY_REASSIGN'
);
if (hasConfigReassign) {
return from(getAgent(soClient, esClient, agent.id)).pipe(
return from(getAgent(esClient, agent.id)).pipe(
concatMap((refreshedAgent) => {
if (!refreshedAgent.policy_id) {
throw new Error('Agent does not have a policy assigned');

View file

@ -5,16 +5,59 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { SearchResponse } from 'elasticsearch';
import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types';
import { appContextService, agentPolicyService } from '../../services';
import { FleetServerAgent, isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common';
import { AGENT_SAVED_OBJECT_TYPE, AGENTS_INDEX } from '../../constants';
import { ESSearchHit } from '../../../../../typings/elasticsearch';
import { escapeSearchQueryPhrase, normalizeKuery } from '../saved_object';
import { esKuery, KueryNode } from '../../../../../../src/plugins/data/server';
import { IngestManagerError, isESClientError, AgentNotFoundError } from '../../errors';
import * as crudServiceSO from './crud_so';
import * as crudServiceFleetServer from './crud_fleet_server';
import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers';
const ACTIVE_AGENT_CONDITION = 'active:true';
const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`;
function _joinFilters(filters: Array<string | undefined | KueryNode>): KueryNode | undefined {
try {
return filters
.filter((filter) => filter !== undefined)
.reduce((acc: KueryNode | undefined, kuery: string | KueryNode | undefined):
| KueryNode
| undefined => {
if (kuery === undefined) {
return acc;
}
const kueryNode: KueryNode =
typeof kuery === 'string'
? esKuery.fromKueryExpression(removeSOAttributes(kuery))
: kuery;
if (!acc) {
return kueryNode;
}
return {
type: 'function',
function: 'and',
arguments: [acc, kueryNode],
};
}, undefined as KueryNode | undefined);
} catch (err) {
throw new IngestManagerError(`Kuery is malformed: ${err.message}`);
}
}
export function removeSOAttributes(kuery: string) {
return kuery.replace(/attributes\./g, '').replace(/fleet-agents\./g, '');
}
export async function listAgents(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
options: ListWithKuery & {
showInactive: boolean;
@ -25,15 +68,58 @@ export async function listAgents(
page: number;
perPage: number;
}> {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
const {
page = 1,
perPage = 20,
sortField = 'enrolled_at',
sortOrder = 'desc',
kuery,
showInactive = false,
showUpgradeable,
} = options;
const filters = [];
return fleetServerEnabled
? crudServiceFleetServer.listAgents(esClient, options)
: crudServiceSO.listAgents(soClient, options);
if (kuery && kuery !== '') {
filters.push(kuery);
}
if (showInactive === false) {
filters.push(ACTIVE_AGENT_CONDITION);
}
const kueryNode = _joinFilters(filters);
const body = kueryNode ? { query: esKuery.toElasticsearchQuery(kueryNode) } : {};
const res = await esClient.search({
index: AGENTS_INDEX,
from: (page - 1) * perPage,
size: perPage,
sort: `${sortField}:${sortOrder}`,
track_total_hits: true,
body,
});
let agentResults: Agent[] = res.body.hits.hits.map(searchHitToAgent);
let total = res.body.hits.total.value;
// filtering for a range on the version string will not work,
// nor does filtering on a flattened field (local_metadata), so filter here
if (showUpgradeable) {
agentResults = agentResults.filter((agent) =>
isAgentUpgradeable(agent, appContextService.getKibanaVersion())
);
total = agentResults.length;
}
return {
agents: res.body.hits.hits.map(searchHitToAgent),
total,
page,
perPage,
};
}
export async function listAllAgents(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
options: Omit<ListWithKuery, 'page' | 'perPage'> & {
showInactive: boolean;
@ -42,45 +128,157 @@ export async function listAllAgents(
agents: Agent[];
total: number;
}> {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
const res = await listAgents(esClient, { ...options, page: 1, perPage: SO_SEARCH_LIMIT });
return fleetServerEnabled
? crudServiceFleetServer.listAllAgents(esClient, options)
: crudServiceSO.listAllAgents(soClient, options);
return {
agents: res.agents,
total: res.total,
};
}
export async function countInactiveAgents(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
options: Pick<ListWithKuery, 'kuery'>
): Promise<number> {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
const { kuery } = options;
const filters = [INACTIVE_AGENT_CONDITION];
return fleetServerEnabled
? crudServiceFleetServer.countInactiveAgents(esClient, options)
: crudServiceSO.countInactiveAgents(soClient, options);
if (kuery && kuery !== '') {
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
}
const kueryNode = _joinFilters(filters);
const body = kueryNode ? { query: esKuery.toElasticsearchQuery(kueryNode) } : {};
const res = await esClient.search({
index: AGENTS_INDEX,
size: 0,
track_total_hits: true,
body,
});
return res.body.hits.total.value;
}
export async function getAgent(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string
) {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return fleetServerEnabled
? crudServiceFleetServer.getAgent(esClient, agentId)
: crudServiceSO.getAgent(soClient, agentId);
export async function getAgent(esClient: ElasticsearchClient, agentId: string) {
try {
const agentHit = await esClient.get<ESSearchHit<FleetServerAgent>>({
index: AGENTS_INDEX,
id: agentId,
});
const agent = searchHitToAgent(agentHit.body);
return agent;
} catch (err) {
if (isESClientError(err) && err.meta.statusCode === 404) {
throw new AgentNotFoundError(`Agent ${agentId} not found`);
}
throw err;
}
}
export async function getAgents(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentIds: string[]
): Promise<Agent[]> {
const body = { docs: agentIds.map((_id) => ({ _id })) };
const res = await esClient.mget({
body,
index: AGENTS_INDEX,
});
const agents = res.body.docs.map(searchHitToAgent);
return agents;
}
export async function getAgentByAccessAPIKeyId(
esClient: ElasticsearchClient,
accessAPIKeyId: string
): Promise<Agent> {
const res = await esClient.search<SearchResponse<FleetServerAgent>>({
index: AGENTS_INDEX,
q: `access_api_key_id:${escapeSearchQueryPhrase(accessAPIKeyId)}`,
});
const [agent] = res.body.hits.hits.map(searchHitToAgent);
if (!agent) {
throw new AgentNotFoundError('Agent not found');
}
if (agent.access_api_key_id !== accessAPIKeyId) {
throw new Error('Agent api key id is not matching');
}
if (!agent.active) {
throw Boom.forbidden('Agent inactive');
}
return agent;
}
export async function updateAgent(
esClient: ElasticsearchClient,
agentId: string,
data: Partial<AgentSOAttributes>
) {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return fleetServerEnabled
? crudServiceFleetServer.getAgents(esClient, agentIds)
: crudServiceSO.getAgents(soClient, agentIds);
await esClient.update({
id: agentId,
index: AGENTS_INDEX,
body: { doc: agentSOAttributesToFleetServerAgentDoc(data) },
refresh: 'wait_for',
});
}
export async function bulkUpdateAgents(
esClient: ElasticsearchClient,
updateData: Array<{
agentId: string;
data: Partial<AgentSOAttributes>;
}>
) {
if (updateData.length === 0) {
return { items: [] };
}
const body = updateData.flatMap(({ agentId, data }) => [
{
update: {
_id: agentId,
},
},
{
doc: { ...agentSOAttributesToFleetServerAgentDoc(data) },
},
]);
const res = await esClient.bulk({
body,
index: AGENTS_INDEX,
refresh: 'wait_for',
});
return {
items: res.body.items.map((item: { update: { _id: string; error?: Error } }) => ({
id: item.update._id,
success: !item.update.error,
error: item.update.error,
})),
};
}
export async function deleteAgent(esClient: ElasticsearchClient, agentId: string) {
try {
await esClient.update({
id: agentId,
index: AGENTS_INDEX,
body: {
doc: { active: false },
},
});
} catch (err) {
if (isESClientError(err) && err.meta.statusCode === 404) {
throw new AgentNotFoundError('Agent not found');
}
throw err;
}
}
export async function getAgentPolicyForAgent(
@ -88,7 +286,7 @@ export async function getAgentPolicyForAgent(
esClient: ElasticsearchClient,
agentId: string
) {
const agent = await getAgent(soClient, esClient, agentId);
const agent = await getAgent(esClient, agentId);
if (!agent.policy_id) {
return;
}
@ -98,51 +296,3 @@ export async function getAgentPolicyForAgent(
return agentPolicy;
}
}
export async function getAgentByAccessAPIKeyId(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
accessAPIKeyId: string
): Promise<Agent> {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return fleetServerEnabled
? crudServiceFleetServer.getAgentByAccessAPIKeyId(esClient, accessAPIKeyId)
: crudServiceSO.getAgentByAccessAPIKeyId(soClient, accessAPIKeyId);
}
export async function updateAgent(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string,
data: Partial<AgentSOAttributes>
) {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return fleetServerEnabled
? crudServiceFleetServer.updateAgent(esClient, agentId, data)
: crudServiceSO.updateAgent(soClient, agentId, data);
}
export async function bulkUpdateAgents(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
data: Array<{
agentId: string;
data: Partial<AgentSOAttributes>;
}>
) {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return fleetServerEnabled
? crudServiceFleetServer.bulkUpdateAgents(esClient, data)
: crudServiceSO.bulkUpdateAgents(soClient, data);
}
export async function deleteAgent(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string
) {
const fleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return fleetServerEnabled
? crudServiceFleetServer.deleteAgent(esClient, agentId)
: crudServiceSO.deleteAgent(soClient, agentId);
}

View file

@ -1,260 +0,0 @@
/*
* 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 Boom from '@hapi/boom';
import type { SearchResponse } from 'elasticsearch';
import type { ElasticsearchClient } from 'src/core/server';
import { isAgentUpgradeable, SO_SEARCH_LIMIT } from '../../../common';
import type { FleetServerAgent } from '../../../common';
import { AGENT_SAVED_OBJECT_TYPE, AGENTS_INDEX } from '../../constants';
import type { ESSearchHit } from '../../../../../typings/elasticsearch';
import type { AgentSOAttributes, Agent, ListWithKuery } from '../../types';
import { escapeSearchQueryPhrase, normalizeKuery } from '../saved_object';
import { appContextService } from '../../services';
import { esKuery } from '../../../../../../src/plugins/data/server';
import type { KueryNode } from '../../../../../../src/plugins/data/server';
import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers';
const ACTIVE_AGENT_CONDITION = 'active:true';
const INACTIVE_AGENT_CONDITION = `NOT (${ACTIVE_AGENT_CONDITION})`;
function _joinFilters(filters: Array<string | undefined | KueryNode>): KueryNode | undefined {
return filters
.filter((filter) => filter !== undefined)
.reduce((acc: KueryNode | undefined, kuery: string | KueryNode | undefined):
| KueryNode
| undefined => {
if (kuery === undefined) {
return acc;
}
const kueryNode: KueryNode =
typeof kuery === 'string' ? esKuery.fromKueryExpression(removeSOAttributes(kuery)) : kuery;
if (!acc) {
return kueryNode;
}
return {
type: 'function',
function: 'and',
arguments: [acc, kueryNode],
};
}, undefined as KueryNode | undefined);
}
export function removeSOAttributes(kuery: string) {
return kuery.replace(/attributes\./g, '').replace(/fleet-agents\./g, '');
}
export async function listAgents(
esClient: ElasticsearchClient,
options: ListWithKuery & {
showInactive: boolean;
}
): Promise<{
agents: Agent[];
total: number;
page: number;
perPage: number;
}> {
const {
page = 1,
perPage = 20,
sortField = 'enrolled_at',
sortOrder = 'desc',
kuery,
showInactive = false,
showUpgradeable,
} = options;
const filters = [];
if (kuery && kuery !== '') {
filters.push(kuery);
}
if (showInactive === false) {
filters.push(ACTIVE_AGENT_CONDITION);
}
const kueryNode = _joinFilters(filters);
const body = kueryNode ? { query: esKuery.toElasticsearchQuery(kueryNode) } : {};
const res = await esClient.search({
index: AGENTS_INDEX,
from: (page - 1) * perPage,
size: perPage,
sort: `${sortField}:${sortOrder}`,
track_total_hits: true,
body,
});
let agentResults: Agent[] = res.body.hits.hits.map(searchHitToAgent);
let total = res.body.hits.total.value;
// filtering for a range on the version string will not work,
// nor does filtering on a flattened field (local_metadata), so filter here
if (showUpgradeable) {
agentResults = agentResults.filter((agent) =>
isAgentUpgradeable(agent, appContextService.getKibanaVersion())
);
total = agentResults.length;
}
return {
agents: res.body.hits.hits.map(searchHitToAgent),
total,
page,
perPage,
};
}
export async function listAllAgents(
esClient: ElasticsearchClient,
options: Omit<ListWithKuery, 'page' | 'perPage'> & {
showInactive: boolean;
}
): Promise<{
agents: Agent[];
total: number;
}> {
const res = await listAgents(esClient, { ...options, page: 1, perPage: SO_SEARCH_LIMIT });
return {
agents: res.agents,
total: res.total,
};
}
export async function countInactiveAgents(
esClient: ElasticsearchClient,
options: Pick<ListWithKuery, 'kuery'>
): Promise<number> {
const { kuery } = options;
const filters = [INACTIVE_AGENT_CONDITION];
if (kuery && kuery !== '') {
filters.push(normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery));
}
const kueryNode = _joinFilters(filters);
const body = kueryNode ? { query: esKuery.toElasticsearchQuery(kueryNode) } : {};
const res = await esClient.search({
index: AGENTS_INDEX,
size: 0,
track_total_hits: true,
body,
});
return res.body.hits.total.value;
}
export async function getAgent(esClient: ElasticsearchClient, agentId: string) {
const agentHit = await esClient.get<ESSearchHit<FleetServerAgent>>({
index: AGENTS_INDEX,
id: agentId,
});
const agent = searchHitToAgent(agentHit.body);
return agent;
}
export async function getAgents(
esClient: ElasticsearchClient,
agentIds: string[]
): Promise<Agent[]> {
const body = { docs: agentIds.map((_id) => ({ _id })) };
const res = await esClient.mget({
body,
index: AGENTS_INDEX,
});
const agents = res.body.docs.map(searchHitToAgent);
return agents;
}
export async function getAgentByAccessAPIKeyId(
esClient: ElasticsearchClient,
accessAPIKeyId: string
): Promise<Agent> {
const res = await esClient.search<SearchResponse<FleetServerAgent>>({
index: AGENTS_INDEX,
q: `access_api_key_id:${escapeSearchQueryPhrase(accessAPIKeyId)}`,
});
const [agent] = res.body.hits.hits.map(searchHitToAgent);
if (!agent) {
throw Boom.notFound('Agent not found');
}
if (agent.access_api_key_id !== accessAPIKeyId) {
throw new Error('Agent api key id is not matching');
}
if (!agent.active) {
throw Boom.forbidden('Agent inactive');
}
return agent;
}
export async function updateAgent(
esClient: ElasticsearchClient,
agentId: string,
data: Partial<AgentSOAttributes>
) {
await esClient.update({
id: agentId,
index: AGENTS_INDEX,
body: { doc: agentSOAttributesToFleetServerAgentDoc(data) },
refresh: 'wait_for',
});
}
export async function bulkUpdateAgents(
esClient: ElasticsearchClient,
updateData: Array<{
agentId: string;
data: Partial<AgentSOAttributes>;
}>
) {
const body = updateData.flatMap(({ agentId, data }) => [
{
update: {
_id: agentId,
},
},
{
doc: { ...agentSOAttributesToFleetServerAgentDoc(data) },
},
]);
const res = await esClient.bulk({
body,
index: AGENTS_INDEX,
refresh: 'wait_for',
});
return {
items: res.body.items.map((item: { update: { _id: string; error?: Error } }) => ({
id: item.update._id,
success: !item.update.error,
error: item.update.error,
})),
};
}
export async function deleteAgent(esClient: ElasticsearchClient, agentId: string) {
await esClient.update({
id: agentId,
index: AGENT_SAVED_OBJECT_TYPE,
body: {
doc: { active: false },
},
});
}

View file

@ -12,15 +12,13 @@ import semverDiff from 'semver/functions/diff';
import semverLte from 'semver/functions/lte';
import type { SavedObjectsClientContract } from 'src/core/server';
import type { AgentType, Agent, AgentSOAttributes, FleetServerAgent } from '../../types';
import { AGENT_SAVED_OBJECT_TYPE, AGENTS_INDEX } from '../../constants';
import type { AgentType, Agent, FleetServerAgent } from '../../types';
import { AGENTS_INDEX } from '../../constants';
import { IngestManagerError } from '../../errors';
import * as APIKeyService from '../api_keys';
import { agentPolicyService } from '../../services';
import { appContextService } from '../app_context';
import { savedObjectToAgent } from './saved_objects';
export async function enroll(
soClient: SavedObjectsClientContract,
type: AgentType,
@ -35,62 +33,33 @@ export async function enroll(
throw new IngestManagerError(`Cannot enroll in managed policy ${agentPolicyId}`);
}
if (appContextService.getConfig()?.agents?.fleetServerEnabled) {
const esClient = appContextService.getInternalUserESClient();
const esClient = appContextService.getInternalUserESClient();
const agentId = uuid();
const accessAPIKey = await APIKeyService.generateAccessApiKey(soClient, agentId);
const fleetServerAgent: FleetServerAgent = {
active: true,
policy_id: agentPolicyId,
type,
enrolled_at: new Date().toISOString(),
user_provided_metadata: metadata?.userProvided ?? {},
local_metadata: metadata?.local ?? {},
access_api_key_id: accessAPIKey.id,
};
await esClient.create({
index: AGENTS_INDEX,
body: fleetServerAgent,
id: agentId,
refresh: 'wait_for',
});
return {
id: agentId,
current_error_events: [],
packages: [],
...fleetServerAgent,
access_api_key: accessAPIKey.key,
} as Agent;
}
const agentData: AgentSOAttributes = {
const agentId = uuid();
const accessAPIKey = await APIKeyService.generateAccessApiKey(soClient, agentId);
const fleetServerAgent: FleetServerAgent = {
active: true,
policy_id: agentPolicyId,
type,
enrolled_at: new Date().toISOString(),
user_provided_metadata: metadata?.userProvided ?? {},
local_metadata: metadata?.local ?? {},
current_error_events: undefined,
access_api_key_id: undefined,
last_checkin: undefined,
default_api_key: undefined,
};
const agent = savedObjectToAgent(
await soClient.create<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agentData, {
refresh: false,
})
);
const accessAPIKey = await APIKeyService.generateAccessApiKey(soClient, agent.id);
await soClient.update<AgentSOAttributes>(AGENT_SAVED_OBJECT_TYPE, agent.id, {
access_api_key_id: accessAPIKey.id,
};
await esClient.create({
index: AGENTS_INDEX,
body: fleetServerAgent,
id: agentId,
refresh: 'wait_for',
});
return { ...agent, access_api_key: accessAPIKey.key };
return {
id: agentId,
current_error_events: [],
packages: [],
...fleetServerAgent,
access_api_key: accessAPIKey.key,
} as Agent;
}
export function validateAgentVersion(

View file

@ -8,31 +8,31 @@
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
import type { SavedObject } from 'kibana/server';
import type { Agent, AgentPolicy } from '../../types';
import type { AgentPolicy } from '../../types';
import { AgentReassignmentError } from '../../errors';
import { reassignAgent, reassignAgents } from './reassign';
const agentInManagedSO = {
id: 'agent-in-managed-policy',
attributes: { policy_id: 'managed-agent-policy' },
} as SavedObject<Agent>;
const agentInManagedSO2 = {
id: 'agent-in-managed-policy2',
attributes: { policy_id: 'managed-agent-policy' },
} as SavedObject<Agent>;
const agentInUnmanagedSO = {
id: 'agent-in-unmanaged-policy',
attributes: { policy_id: 'unmanaged-agent-policy' },
} as SavedObject<Agent>;
const agentInUnmanagedSO2 = {
id: 'agent-in-unmanaged-policy2',
attributes: { policy_id: 'unmanaged-agent-policy' },
} as SavedObject<Agent>;
const agentInManagedDoc = {
_id: 'agent-in-managed-policy',
_source: { policy_id: 'managed-agent-policy' },
};
const agentInManagedDoc2 = {
_id: 'agent-in-managed-policy2',
_source: { policy_id: 'managed-agent-policy' },
};
const agentInUnmanagedDoc = {
_id: 'agent-in-unmanaged-policy',
_source: { policy_id: 'unmanaged-agent-policy' },
};
const unmanagedAgentPolicySO = {
id: 'unmanaged-agent-policy',
attributes: { is_managed: false },
} as SavedObject<AgentPolicy>;
const unmanagedAgentPolicySO2 = {
id: 'unmanaged-agent-policy-2',
attributes: { is_managed: false },
} as SavedObject<AgentPolicy>;
const managedAgentPolicySO = {
id: 'managed-agent-policy',
attributes: { is_managed: true },
@ -40,70 +40,70 @@ const managedAgentPolicySO = {
describe('reassignAgent (singular)', () => {
it('can reassign from unmanaged policy to unmanaged', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
await reassignAgent(soClient, esClient, agentInUnmanagedSO.id, agentInUnmanagedSO2.id);
const { soClient, esClient } = createClientsMock();
await reassignAgent(soClient, esClient, agentInUnmanagedDoc._id, unmanagedAgentPolicySO.id);
// calls ES update with correct values
expect(soClient.update).toBeCalledTimes(1);
const calledWith = soClient.update.mock.calls[0];
expect(calledWith[1]).toBe(agentInUnmanagedSO.id);
expect(calledWith[2]).toHaveProperty('policy_id', agentInUnmanagedSO2.id);
expect(esClient.update).toBeCalledTimes(1);
const calledWith = esClient.update.mock.calls[0];
expect(calledWith[0]?.id).toBe(agentInUnmanagedDoc._id);
// @ts-expect-error
expect(calledWith[0]?.body?.doc).toHaveProperty('policy_id', unmanagedAgentPolicySO.id);
});
it('cannot reassign from unmanaged policy to managed', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const { soClient, esClient } = createClientsMock();
await expect(
reassignAgent(
soClient,
esClient,
agentInUnmanagedSO.id,
agentInManagedSO.attributes.policy_id!
)
reassignAgent(soClient, esClient, agentInUnmanagedDoc._id, managedAgentPolicySO.id)
).rejects.toThrowError(AgentReassignmentError);
// does not call ES update
expect(soClient.update).toBeCalledTimes(0);
expect(esClient.update).toBeCalledTimes(0);
});
it('cannot reassign from managed policy', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const { soClient, esClient } = createClientsMock();
await expect(
reassignAgent(soClient, esClient, agentInManagedSO.id, agentInManagedSO2.id)
reassignAgent(soClient, esClient, agentInManagedDoc._id, unmanagedAgentPolicySO.id)
).rejects.toThrowError(AgentReassignmentError);
// does not call ES update
expect(soClient.update).toBeCalledTimes(0);
expect(esClient.update).toBeCalledTimes(0);
await expect(
reassignAgent(soClient, esClient, agentInManagedSO.id, agentInUnmanagedSO.id)
reassignAgent(soClient, esClient, agentInManagedDoc._id, managedAgentPolicySO.id)
).rejects.toThrowError(AgentReassignmentError);
// does not call ES update
expect(soClient.update).toBeCalledTimes(0);
expect(esClient.update).toBeCalledTimes(0);
});
});
describe('reassignAgents (plural)', () => {
it('agents in managed policies are not updated', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const idsToReassign = [agentInUnmanagedSO.id, agentInManagedSO.id, agentInUnmanagedSO.id];
await reassignAgents(soClient, esClient, { agentIds: idsToReassign }, agentInUnmanagedSO.id);
const { soClient, esClient } = createClientsMock();
const idsToReassign = [agentInUnmanagedDoc._id, agentInManagedDoc._id, agentInManagedDoc2._id];
await reassignAgents(
soClient,
esClient,
{ agentIds: idsToReassign },
unmanagedAgentPolicySO2.id
);
// calls ES update with correct values
const calledWith = soClient.bulkUpdate.mock.calls[0][0];
const expectedResults = [agentInUnmanagedSO.id, agentInUnmanagedSO.id];
expect(calledWith.length).toBe(expectedResults.length); // only 2 are unmanaged
expect(calledWith.map(({ id }) => id)).toEqual(expectedResults);
const calledWith = esClient.bulk.mock.calls[0][0];
// only 1 are unmanaged and bulk write two line per update
// @ts-expect-error
expect(calledWith.body.length).toBe(2);
// @ts-expect-error
expect(calledWith.body[0].update._id).toEqual(agentInUnmanagedDoc._id);
});
});
function createClientMock() {
function createClientsMock() {
const soClientMock = savedObjectsClientMock.create();
// need to mock .create & bulkCreate due to (bulk)createAgentAction(s) in reassignAgent(s)
soClientMock.create.mockResolvedValue(agentInUnmanagedSO);
// @ts-expect-error
soClientMock.create.mockResolvedValue({ attributes: { agent_id: 'test' } });
soClientMock.bulkCreate.mockImplementation(async ([{ type, attributes }]) => {
return {
saved_objects: [await soClientMock.create(type, attributes)],
@ -112,26 +112,48 @@ function createClientMock() {
soClientMock.bulkUpdate.mockResolvedValue({
saved_objects: [],
});
soClientMock.get.mockImplementation(async (_, id) => {
switch (id) {
case unmanagedAgentPolicySO.id:
return unmanagedAgentPolicySO;
case managedAgentPolicySO.id:
return managedAgentPolicySO;
case agentInManagedSO.id:
return agentInManagedSO;
case agentInUnmanagedSO.id:
case unmanagedAgentPolicySO2.id:
return unmanagedAgentPolicySO2;
default:
return agentInUnmanagedSO;
throw new Error('Not found');
}
});
soClientMock.bulkGet.mockImplementation(async (options) => {
return {
saved_objects: await Promise.all(options!.map(({ type, id }) => soClientMock.get(type, id))),
};
});
return soClientMock;
const esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser;
// @ts-expect-error
esClientMock.mget.mockImplementation(async () => {
return {
body: {
docs: [agentInManagedDoc, agentInUnmanagedDoc, agentInManagedDoc2],
},
};
});
// @ts-expect-error
esClientMock.get.mockImplementation(async ({ id }) => {
switch (id) {
case agentInManagedDoc._id:
return { body: agentInManagedDoc };
case agentInUnmanagedDoc._id:
return { body: agentInUnmanagedDoc };
default:
throw new Error('Not found');
}
});
// @ts-expect-error
esClientMock.bulk.mockResolvedValue({
body: { items: [] },
});
return { soClient: soClientMock, esClient: esClientMock };
}

View file

@ -33,7 +33,7 @@ export async function reassignAgent(
await reassignAgentIsAllowed(soClient, esClient, agentId, newAgentPolicyId);
await updateAgent(soClient, esClient, agentId, {
await updateAgent(esClient, agentId, {
policy_id: newAgentPolicyId,
policy_revision: null,
});
@ -79,7 +79,7 @@ export async function reassignAgents(
kuery: string;
},
newAgentPolicyId: string
): Promise<{ items: Array<{ id: string; sucess: boolean; error?: Error }> }> {
): Promise<{ items: Array<{ id: string; success: boolean; error?: Error }> }> {
const agentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId);
if (!agentPolicy) {
throw Boom.notFound(`Agent policy not found: ${newAgentPolicyId}`);
@ -88,9 +88,9 @@ export async function reassignAgents(
// Filter to agents that do not already use the new agent policy ID
const agents =
'agentIds' in options
? await getAgents(soClient, esClient, options.agentIds)
? await getAgents(esClient, options.agentIds)
: (
await listAllAgents(soClient, esClient, {
await listAllAgents(esClient, {
kuery: options.kuery,
showInactive: false,
})
@ -106,7 +106,6 @@ export async function reassignAgents(
);
const res = await bulkUpdateAgents(
soClient,
esClient,
agentsToUpdate.map((agent) => ({
agentId: agent.id,

View file

@ -5,79 +5,80 @@
* 2.0.
*/
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
import type { SavedObject } from 'kibana/server';
import { AGENT_TYPE_PERMANENT } from '../../../common/constants';
import type { AgentSOAttributes } from '../../../common/types/models';
import { elasticsearchServiceMock } from 'src/core/server/mocks';
import { getAgentStatusById } from './status';
describe('Agent status service', () => {
it('should return inactive when agent is not active', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
id: 'id',
type: AGENT_TYPE_PERMANENT,
attributes: {
active: false,
local_metadata: {},
user_provided_metadata: {},
// @ts-expect-error
mockElasticsearchClient.get.mockResolvedValue({
body: {
_id: 'id',
_source: {
active: false,
local_metadata: {},
user_provided_metadata: {},
},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
});
const status = await getAgentStatusById(mockElasticsearchClient, 'id');
expect(status).toEqual('inactive');
});
it('should return online when agent is active', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
id: 'id',
type: AGENT_TYPE_PERMANENT,
attributes: {
active: true,
last_checkin: new Date().toISOString(),
local_metadata: {},
user_provided_metadata: {},
// @ts-expect-error
mockElasticsearchClient.get.mockResolvedValue({
body: {
_id: 'id',
_source: {
active: true,
last_checkin: new Date().toISOString(),
local_metadata: {},
user_provided_metadata: {},
},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
});
const status = await getAgentStatusById(mockElasticsearchClient, 'id');
expect(status).toEqual('online');
});
it('should return enrolling when agent is active but never checkin', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
id: 'id',
type: AGENT_TYPE_PERMANENT,
attributes: {
active: true,
local_metadata: {},
user_provided_metadata: {},
// @ts-expect-error
mockElasticsearchClient.get.mockResolvedValue({
body: {
_id: 'id',
_source: {
active: true,
local_metadata: {},
user_provided_metadata: {},
},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
});
const status = await getAgentStatusById(mockElasticsearchClient, 'id');
expect(status).toEqual('enrolling');
});
it('should return unenrolling when agent is unenrolling', async () => {
const mockSavedObjectsClient = savedObjectsClientMock.create();
const mockElasticsearchClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
mockSavedObjectsClient.get = jest.fn().mockReturnValue({
id: 'id',
type: AGENT_TYPE_PERMANENT,
attributes: {
active: true,
last_checkin: new Date().toISOString(),
unenrollment_started_at: new Date().toISOString(),
local_metadata: {},
user_provided_metadata: {},
// @ts-expect-error
mockElasticsearchClient.get.mockResolvedValue({
body: {
_id: 'id',
_source: {
active: true,
last_checkin: new Date().toISOString(),
unenrollment_started_at: new Date().toISOString(),
local_metadata: {},
user_provided_metadata: {},
},
},
} as SavedObject<AgentSOAttributes>);
const status = await getAgentStatusById(mockSavedObjectsClient, mockElasticsearchClient, 'id');
});
const status = await getAgentStatusById(mockElasticsearchClient, 'id');
expect(status).toEqual('unenrolling');
});
});

View file

@ -13,26 +13,20 @@ import type { AgentStatus } from '../../types';
import { AgentStatusKueryHelper } from '../../../common/services';
import { esKuery } from '../../../../../../src/plugins/data/server';
import type { KueryNode } from '../../../../../../src/plugins/data/server';
import { normalizeKuery } from '../saved_object';
import { appContextService } from '../app_context';
import { getAgent, listAgents } from './crud';
import { removeSOAttributes } from './crud_fleet_server';
import { getAgent, listAgents, removeSOAttributes } from './crud';
export async function getAgentStatusById(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string
): Promise<AgentStatus> {
const agent = await getAgent(soClient, esClient, agentId);
const agent = await getAgent(esClient, agentId);
return AgentStatusKueryHelper.getAgentStatus(agent);
}
export const getAgentStatus = AgentStatusKueryHelper.getAgentStatus;
function joinKuerys(...kuerys: Array<string | undefined>) {
const isFleetServerEnabled = appContextService.getConfig()?.agents?.fleetServerEnabled;
return kuerys
.filter((kuery) => kuery !== undefined)
.reduce((acc: KueryNode | undefined, kuery: string | undefined): KueryNode | undefined => {
@ -40,9 +34,7 @@ function joinKuerys(...kuerys: Array<string | undefined>) {
return acc;
}
const normalizedKuery: KueryNode = esKuery.fromKueryExpression(
isFleetServerEnabled
? removeSOAttributes(kuery || '')
: normalizeKuery(AGENT_SAVED_OBJECT_TYPE, kuery || '')
removeSOAttributes(kuery || '')
);
if (!acc) {
@ -72,7 +64,7 @@ export async function getAgentStatusForAgentPolicy(
AgentStatusKueryHelper.buildKueryForUpdatingAgents(),
],
(kuery) =>
listAgents(soClient, esClient, {
listAgents(esClient, {
showInactive: false,
perPage: 0,
page: 1,

View file

@ -8,23 +8,23 @@
import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/server/mocks';
import type { SavedObject } from 'kibana/server';
import type { Agent, AgentPolicy } from '../../types';
import type { AgentPolicy } from '../../types';
import { AgentUnenrollmentError } from '../../errors';
import { unenrollAgent, unenrollAgents } from './unenroll';
const agentInManagedSO = {
id: 'agent-in-managed-policy',
attributes: { policy_id: 'managed-agent-policy' },
} as SavedObject<Agent>;
const agentInUnmanagedSO = {
id: 'agent-in-unmanaged-policy',
attributes: { policy_id: 'unmanaged-agent-policy' },
} as SavedObject<Agent>;
const agentInUnmanagedSO2 = {
id: 'agent-in-unmanaged-policy2',
attributes: { policy_id: 'unmanaged-agent-policy' },
} as SavedObject<Agent>;
const agentInManagedDoc = {
_id: 'agent-in-managed-policy',
_source: { policy_id: 'managed-agent-policy' },
};
const agentInUnmanagedDoc = {
_id: 'agent-in-unmanaged-policy',
_source: { policy_id: 'unmanaged-agent-policy' },
};
const agentInUnmanagedDoc2 = {
_id: 'agent-in-unmanaged-policy2',
_source: { policy_id: 'unmanaged-agent-policy' },
};
const unmanagedAgentPolicySO = {
id: 'unmanaged-agent-policy',
attributes: { is_managed: false },
@ -36,57 +36,69 @@ const managedAgentPolicySO = {
describe('unenrollAgent (singular)', () => {
it('can unenroll from unmanaged policy', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
await unenrollAgent(soClient, esClient, agentInUnmanagedSO.id);
const { soClient, esClient } = createClientMock();
await unenrollAgent(soClient, esClient, agentInUnmanagedDoc._id);
// calls ES update with correct values
expect(soClient.update).toBeCalledTimes(1);
const calledWith = soClient.update.mock.calls[0];
expect(calledWith[1]).toBe(agentInUnmanagedSO.id);
expect(calledWith[2]).toHaveProperty('unenrollment_started_at');
expect(esClient.update).toBeCalledTimes(1);
const calledWith = esClient.update.mock.calls[0];
expect(calledWith[0]?.id).toBe(agentInUnmanagedDoc._id);
expect(calledWith[0]?.body).toHaveProperty('doc.unenrollment_started_at');
});
it('cannot unenroll from managed policy', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
await expect(unenrollAgent(soClient, esClient, agentInManagedSO.id)).rejects.toThrowError(
const { soClient, esClient } = createClientMock();
await expect(unenrollAgent(soClient, esClient, agentInManagedDoc._id)).rejects.toThrowError(
AgentUnenrollmentError
);
// does not call ES update
expect(soClient.update).toBeCalledTimes(0);
expect(esClient.update).toBeCalledTimes(0);
});
});
describe('unenrollAgents (plural)', () => {
it('can unenroll from an unmanaged policy', async () => {
const soClient = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const idsToUnenroll = [agentInUnmanagedSO.id, agentInUnmanagedSO2.id];
const { soClient, esClient } = createClientMock();
const idsToUnenroll = [agentInUnmanagedDoc._id, agentInUnmanagedDoc2._id];
await unenrollAgents(soClient, esClient, { agentIds: idsToUnenroll });
// calls ES update with correct values
const calledWith = soClient.bulkUpdate.mock.calls[0][0];
expect(calledWith.length).toBe(idsToUnenroll.length);
expect(calledWith.map(({ id }) => id)).toEqual(idsToUnenroll);
for (const params of calledWith) {
expect(params.attributes).toHaveProperty('unenrollment_started_at');
const calledWith = esClient.bulk.mock.calls[1][0];
const ids = calledWith?.body
// @ts-expect-error
.filter((i: any) => i.update !== undefined)
.map((i: any) => i.update._id);
// @ts-expect-error
const docs = calledWith?.body.filter((i: any) => i.doc).map((i: any) => i.doc);
expect(ids).toHaveLength(2);
expect(ids).toEqual(idsToUnenroll);
for (const doc of docs) {
expect(doc).toHaveProperty('unenrollment_started_at');
}
});
it('cannot unenroll from a managed policy', async () => {
const soClient = createClientMock();
const { soClient, esClient } = createClientMock();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const idsToUnenroll = [agentInUnmanagedSO.id, agentInManagedSO.id, agentInUnmanagedSO2.id];
const idsToUnenroll = [
agentInUnmanagedDoc._id,
agentInManagedDoc._id,
agentInUnmanagedDoc2._id,
];
await unenrollAgents(soClient, esClient, { agentIds: idsToUnenroll });
// calls ES update with correct values
const calledWith = soClient.bulkUpdate.mock.calls[0][0];
const onlyUnmanaged = [agentInUnmanagedSO.id, agentInUnmanagedSO2.id];
expect(calledWith.length).toBe(onlyUnmanaged.length);
expect(calledWith.map(({ id }) => id)).toEqual(onlyUnmanaged);
for (const params of calledWith) {
expect(params.attributes).toHaveProperty('unenrollment_started_at');
const onlyUnmanaged = [agentInUnmanagedDoc._id, agentInUnmanagedDoc2._id];
const calledWith = esClient.bulk.mock.calls[1][0];
const ids = calledWith?.body
// @ts-expect-error
.filter((i: any) => i.update !== undefined)
.map((i: any) => i.update._id);
// @ts-expect-error
const docs = calledWith?.body.filter((i: any) => i.doc).map((i: any) => i.doc);
expect(ids).toHaveLength(onlyUnmanaged.length);
expect(ids).toEqual(onlyUnmanaged);
for (const doc of docs) {
expect(doc).toHaveProperty('unenrollment_started_at');
}
});
});
@ -95,7 +107,8 @@ function createClientMock() {
const soClientMock = savedObjectsClientMock.create();
// need to mock .create & bulkCreate due to (bulk)createAgentAction(s) in unenrollAgent(s)
soClientMock.create.mockResolvedValue(agentInUnmanagedSO);
// @ts-expect-error
soClientMock.create.mockResolvedValue({ attributes: { agent_id: 'tata' } });
soClientMock.bulkCreate.mockImplementation(async ([{ type, attributes }]) => {
return {
saved_objects: [await soClientMock.create(type, attributes)],
@ -111,13 +124,8 @@ function createClientMock() {
return unmanagedAgentPolicySO;
case managedAgentPolicySO.id:
return managedAgentPolicySO;
case agentInManagedSO.id:
return agentInManagedSO;
case agentInUnmanagedSO2.id:
return agentInUnmanagedSO2;
case agentInUnmanagedSO.id:
default:
return agentInUnmanagedSO;
throw new Error('not found');
}
});
@ -127,5 +135,46 @@ function createClientMock() {
};
});
return soClientMock;
const esClientMock = elasticsearchServiceMock.createClusterClient().asInternalUser;
// @ts-expect-error
esClientMock.get.mockImplementation(async ({ id }) => {
switch (id) {
case agentInManagedDoc._id:
return { body: agentInManagedDoc };
case agentInUnmanagedDoc2._id:
return { body: agentInUnmanagedDoc2 };
case agentInUnmanagedDoc._id:
return { body: agentInUnmanagedDoc };
default:
throw new Error('not found');
}
});
// @ts-expect-error
esClientMock.bulk.mockResolvedValue({
body: { items: [] },
});
// @ts-expect-error
esClientMock.mget.mockImplementation(async (params) => {
// @ts-expect-error
const docs = params?.body.docs.map(({ _id }) => {
switch (_id) {
case agentInManagedDoc._id:
return agentInManagedDoc;
case agentInUnmanagedDoc2._id:
return agentInUnmanagedDoc2;
case agentInUnmanagedDoc._id:
return agentInUnmanagedDoc;
default:
throw new Error('not found');
}
});
return {
body: {
docs,
},
};
});
return { soClient: soClientMock, esClient: esClientMock };
}

View file

@ -48,7 +48,7 @@ export async function unenrollAgent(
created_at: now,
type: 'UNENROLL',
});
await updateAgent(soClient, esClient, agentId, {
await updateAgent(esClient, agentId, {
unenrollment_started_at: now,
});
}
@ -66,9 +66,9 @@ export async function unenrollAgents(
) {
const agents =
'agentIds' in options
? await getAgents(soClient, esClient, options.agentIds)
? await getAgents(esClient, options.agentIds)
: (
await listAllAgents(soClient, esClient, {
await listAllAgents(esClient, {
kuery: options.kuery,
showInactive: false,
})
@ -101,7 +101,6 @@ export async function unenrollAgents(
// Update the necessary agents
return bulkUpdateAgents(
soClient,
esClient,
agentsToUpdate.map((agent) => ({
agentId: agent.id,
@ -117,7 +116,7 @@ export async function forceUnenrollAgent(
esClient: ElasticsearchClient,
agentId: string
) {
const agent = await getAgent(soClient, esClient, agentId);
const agent = await getAgent(esClient, agentId);
await Promise.all([
agent.access_api_key_id
@ -128,7 +127,7 @@ export async function forceUnenrollAgent(
: undefined,
]);
await updateAgent(soClient, esClient, agentId, {
await updateAgent(esClient, agentId, {
active: false,
unenrolled_at: new Date().toISOString(),
});
@ -148,9 +147,9 @@ export async function forceUnenrollAgents(
// Filter to agents that are not already unenrolled
const agents =
'agentIds' in options
? await getAgents(soClient, esClient, options.agentIds)
? await getAgents(esClient, options.agentIds)
: (
await listAllAgents(soClient, esClient, {
await listAllAgents(esClient, {
kuery: options.kuery,
showInactive: false,
})
@ -175,7 +174,6 @@ export async function forceUnenrollAgents(
}
// Update the necessary agents
return bulkUpdateAgents(
soClient,
esClient,
agentsToUpdate.map((agent) => ({
agentId: agent.id,

View file

@ -20,7 +20,7 @@ export async function unenrollForAgentPolicyId(
let hasMore = true;
let page = 1;
while (hasMore) {
const { agents } = await listAgents(soClient, esClient, {
const { agents } = await listAgents(esClient, {
kuery: `${AGENT_SAVED_OBJECT_TYPE}.policy_id:"${policyId}"`,
page: page++,
perPage: 1000,

View file

@ -56,7 +56,7 @@ export async function sendUpgradeAgentAction({
ack_data: data,
type: 'UPGRADE',
});
await updateAgent(soClient, esClient, agentId, {
await updateAgent(esClient, agentId, {
upgraded_at: null,
upgrade_started_at: now,
});
@ -73,7 +73,7 @@ export async function ackAgentUpgraded(
if (!ackData) throw new Error('data missing from UPGRADE action');
const { version } = JSON.parse(ackData);
if (!version) throw new Error('version missing from UPGRADE action');
await updateAgent(soClient, esClient, agentAction.agent_id, {
await updateAgent(esClient, agentAction.agent_id, {
upgraded_at: new Date().toISOString(),
upgrade_started_at: null,
});
@ -100,9 +100,9 @@ export async function sendUpgradeAgentsActions(
// Filter out agents currently unenrolling, agents unenrolled, and agents not upgradeable
const agents =
'agentIds' in options
? await getAgents(soClient, esClient, options.agentIds)
? await getAgents(esClient, options.agentIds)
: (
await listAllAgents(soClient, esClient, {
await listAllAgents(esClient, {
kuery: options.kuery,
showInactive: false,
})
@ -150,7 +150,6 @@ export async function sendUpgradeAgentsActions(
);
return await bulkUpdateAgents(
soClient,
esClient,
upgradeableAgents.map((agent) => ({
agentId: agent.id,

View file

@ -5,16 +5,21 @@
* 2.0.
*/
import uuid from 'uuid';
import Boom from '@hapi/boom';
import { GetResponse } from 'elasticsearch';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
import type { EnrollmentAPIKey } from '../../types';
import { appContextService } from '../app_context';
import { ESSearchResponse as SearchResponse } from '../../../../../typings/elasticsearch';
import type { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types';
import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
import { agentPolicyService } from '../agent_policy';
import { escapeSearchQueryPhrase } from '../saved_object';
import * as enrollmentApiKeyServiceSO from './enrollment_api_key_so';
import * as enrollmentApiKeyServiceFleetServer from './enrollment_api_key_fleet_server';
import { createAPIKey, invalidateAPIKeys } from './security';
export async function listEnrollmentApiKeys(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
options: {
page?: number;
@ -23,22 +28,44 @@ export async function listEnrollmentApiKeys(
showInactive?: boolean;
}
): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> {
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
return enrollmentApiKeyServiceFleetServer.listEnrollmentApiKeys(esClient, options);
} else {
return enrollmentApiKeyServiceSO.listEnrollmentApiKeys(soClient, options);
}
const { page = 1, perPage = 20, kuery } = options;
const res = await esClient.search<SearchResponse<FleetServerEnrollmentAPIKey, {}>>({
index: ENROLLMENT_API_KEYS_INDEX,
from: (page - 1) * perPage,
size: perPage,
sort: 'created_at:desc',
track_total_hits: true,
q: kuery,
});
const items = res.body.hits.hits.map(esDocToEnrollmentApiKey);
return {
items,
total: res.body.hits.total.value,
page,
perPage,
};
}
export async function getEnrollmentAPIKey(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
id: string
) {
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
return enrollmentApiKeyServiceFleetServer.getEnrollmentAPIKey(esClient, id);
} else {
return enrollmentApiKeyServiceSO.getEnrollmentAPIKey(soClient, id);
): Promise<EnrollmentAPIKey> {
try {
const res = await esClient.get<GetResponse<FleetServerEnrollmentAPIKey>>({
index: ENROLLMENT_API_KEYS_INDEX,
id,
});
return esDocToEnrollmentApiKey(res.body);
} catch (e) {
if (e instanceof ResponseError && e.statusCode === 404) {
throw Boom.notFound(`Enrollment api key ${id} not found`);
}
throw e;
}
}
@ -52,18 +79,44 @@ export async function deleteEnrollmentApiKey(
esClient: ElasticsearchClient,
id: string
) {
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
return enrollmentApiKeyServiceFleetServer.deleteEnrollmentApiKey(soClient, esClient, id);
} else {
return enrollmentApiKeyServiceSO.deleteEnrollmentApiKey(soClient, id);
}
const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id);
await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]);
await esClient.update({
index: ENROLLMENT_API_KEYS_INDEX,
id,
body: {
doc: {
active: false,
},
},
refresh: 'wait_for',
});
}
export async function deleteEnrollmentApiKeyForAgentPolicyId(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentPolicyId: string
) {
return enrollmentApiKeyServiceSO.deleteEnrollmentApiKeyForAgentPolicyId(soClient, agentPolicyId);
let hasMore = true;
let page = 1;
while (hasMore) {
const { items } = await listEnrollmentApiKeys(esClient, {
page: page++,
perPage: 100,
kuery: `policy_id:${agentPolicyId}`,
});
if (items.length === 0) {
hasMore = false;
}
for (const apiKey of items) {
await deleteEnrollmentApiKey(soClient, esClient, apiKey.id);
}
}
}
export async function generateEnrollmentAPIKey(
@ -74,22 +127,91 @@ export async function generateEnrollmentAPIKey(
expiration?: string;
agentPolicyId?: string;
}
) {
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
return enrollmentApiKeyServiceFleetServer.generateEnrollmentAPIKey(soClient, esClient, data);
} else {
return enrollmentApiKeyServiceSO.generateEnrollmentAPIKey(soClient, data);
): Promise<EnrollmentAPIKey> {
const id = uuid.v4();
const { name: providedKeyName } = data;
if (data.agentPolicyId) {
await validateAgentPolicyId(soClient, data.agentPolicyId);
}
const agentPolicyId =
data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient));
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
const key = await createAPIKey(soClient, name, {
// Useless role to avoid to have the privilege of the user that created the key
'fleet-apikey-enroll': {
cluster: [],
applications: [
{
application: '.fleet',
privileges: ['no-privileges'],
resources: ['*'],
},
],
},
});
if (!key) {
throw new Error('Unable to create an enrollment api key');
}
const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64');
const body = {
active: true,
api_key_id: key.id,
api_key: apiKey,
name,
policy_id: agentPolicyId,
created_at: new Date().toISOString(),
};
const res = await esClient.create({
index: ENROLLMENT_API_KEYS_INDEX,
body,
id,
refresh: 'wait_for',
});
return {
id: res.body._id,
...body,
};
}
export async function getEnrollmentAPIKeyById(esClient: ElasticsearchClient, apiKeyId: string) {
const res = await esClient.search<SearchResponse<FleetServerEnrollmentAPIKey, {}>>({
index: ENROLLMENT_API_KEYS_INDEX,
q: `api_key_id:${escapeSearchQueryPhrase(apiKeyId)}`,
});
const [enrollmentAPIKey] = res.body.hits.hits.map(esDocToEnrollmentApiKey);
if (enrollmentAPIKey?.api_key_id !== apiKeyId) {
throw new Error('find enrollmentKeyById returned an incorrect key');
}
return enrollmentAPIKey;
}
async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) {
try {
await agentPolicyService.get(soClient, agentPolicyId);
} catch (e) {
if (e.isBoom && e.output.statusCode === 404) {
throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`);
}
throw e;
}
}
export async function getEnrollmentAPIKeyById(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
apiKeyId: string
) {
if (appContextService.getConfig()?.agents?.fleetServerEnabled === true) {
return enrollmentApiKeyServiceFleetServer.getEnrollmentAPIKeyById(esClient, apiKeyId);
} else {
return enrollmentApiKeyServiceSO.getEnrollmentAPIKeyById(soClient, apiKeyId);
}
function esDocToEnrollmentApiKey(doc: {
_id: string;
_source: FleetServerEnrollmentAPIKey;
}): EnrollmentAPIKey {
return {
id: doc._id,
...doc._source,
created_at: doc._source.created_at as string,
active: doc._source.active || false,
};
}

View file

@ -1,200 +0,0 @@
/*
* 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 uuid from 'uuid';
import Boom from '@hapi/boom';
import type { GetResponse } from 'elasticsearch';
import { ResponseError } from '@elastic/elasticsearch/lib/errors';
import type { SavedObjectsClientContract, ElasticsearchClient } from 'src/core/server';
import type { ESSearchResponse as SearchResponse } from '../../../../../typings/elasticsearch';
import type { EnrollmentAPIKey, FleetServerEnrollmentAPIKey } from '../../types';
import { ENROLLMENT_API_KEYS_INDEX } from '../../constants';
import { agentPolicyService } from '../agent_policy';
import { escapeSearchQueryPhrase } from '../saved_object';
import { createAPIKey, invalidateAPIKeys } from './security';
export async function listEnrollmentApiKeys(
esClient: ElasticsearchClient,
options: {
page?: number;
perPage?: number;
kuery?: string;
showInactive?: boolean;
}
): Promise<{ items: EnrollmentAPIKey[]; total: any; page: any; perPage: any }> {
const { page = 1, perPage = 20, kuery } = options;
const res = await esClient.search<SearchResponse<FleetServerEnrollmentAPIKey, {}>>({
index: ENROLLMENT_API_KEYS_INDEX,
from: (page - 1) * perPage,
size: perPage,
sort: 'created_at:desc',
track_total_hits: true,
q: kuery,
});
const items = res.body.hits.hits.map(esDocToEnrollmentApiKey);
return {
items,
total: res.body.hits.total.value,
page,
perPage,
};
}
export async function getEnrollmentAPIKey(
esClient: ElasticsearchClient,
id: string
): Promise<EnrollmentAPIKey> {
try {
const res = await esClient.get<GetResponse<FleetServerEnrollmentAPIKey>>({
index: ENROLLMENT_API_KEYS_INDEX,
id,
});
return esDocToEnrollmentApiKey(res.body);
} catch (e) {
if (e instanceof ResponseError && e.statusCode === 404) {
throw Boom.notFound(`Enrollment api key ${id} not found`);
}
throw e;
}
}
/**
* Invalidate an api key and mark it as inactive
* @param soClient
* @param id
*/
export async function deleteEnrollmentApiKey(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
id: string
) {
const enrollmentApiKey = await getEnrollmentAPIKey(esClient, id);
await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]);
await esClient.update({
index: ENROLLMENT_API_KEYS_INDEX,
id,
body: {
doc: {
active: false,
},
},
refresh: 'wait_for',
});
}
export async function deleteEnrollmentApiKeyForAgentPolicyId(
soClient: SavedObjectsClientContract,
agentPolicyId: string
) {
throw new Error('NOT IMPLEMENTED');
}
export async function generateEnrollmentAPIKey(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
data: {
name?: string;
expiration?: string;
agentPolicyId?: string;
}
): Promise<EnrollmentAPIKey> {
const id = uuid.v4();
const { name: providedKeyName } = data;
if (data.agentPolicyId) {
await validateAgentPolicyId(soClient, data.agentPolicyId);
}
const agentPolicyId =
data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient));
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
const key = await createAPIKey(soClient, name, {
// Useless role to avoid to have the privilege of the user that created the key
'fleet-apikey-enroll': {
cluster: [],
applications: [
{
application: '.fleet',
privileges: ['no-privileges'],
resources: ['*'],
},
],
},
});
if (!key) {
throw new Error('Unable to create an enrollment api key');
}
const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64');
const body = {
active: true,
api_key_id: key.id,
api_key: apiKey,
name,
policy_id: agentPolicyId,
created_at: new Date().toISOString(),
};
const res = await esClient.create({
index: ENROLLMENT_API_KEYS_INDEX,
body,
id,
refresh: 'wait_for',
});
return {
id: res.body._id,
...body,
};
}
export async function getEnrollmentAPIKeyById(esClient: ElasticsearchClient, apiKeyId: string) {
const res = await esClient.search<SearchResponse<FleetServerEnrollmentAPIKey, {}>>({
index: ENROLLMENT_API_KEYS_INDEX,
q: `api_key_id:${escapeSearchQueryPhrase(apiKeyId)}`,
});
const [enrollmentAPIKey] = res.body.hits.hits.map(esDocToEnrollmentApiKey);
if (enrollmentAPIKey?.api_key_id !== apiKeyId) {
throw new Error('find enrollmentKeyById returned an incorrect key');
}
return enrollmentAPIKey;
}
async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) {
try {
await agentPolicyService.get(soClient, agentPolicyId);
} catch (e) {
if (e.isBoom && e.output.statusCode === 404) {
throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`);
}
throw e;
}
}
function esDocToEnrollmentApiKey(doc: {
_id: string;
_source: FleetServerEnrollmentAPIKey;
}): EnrollmentAPIKey {
return {
id: doc._id,
...doc._source,
created_at: doc._source.created_at as string,
active: doc._source.active || false,
};
}

View file

@ -5,17 +5,12 @@
* 2.0.
*/
import uuid from 'uuid';
import Boom from '@hapi/boom';
import type { SavedObjectsClientContract, SavedObject } from 'src/core/server';
import type { EnrollmentAPIKey, EnrollmentAPIKeySOAttributes } from '../../types';
import { ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE } from '../../constants';
import { agentPolicyService } from '../agent_policy';
import { appContextService } from '../app_context';
import { normalizeKuery, escapeSearchQueryPhrase } from '../saved_object';
import { createAPIKey, invalidateAPIKeys } from './security';
import { normalizeKuery } from '../saved_object';
export async function listEnrollmentApiKeys(
soClient: SavedObjectsClientContract,
@ -61,125 +56,6 @@ export async function getEnrollmentAPIKey(soClient: SavedObjectsClientContract,
return savedObjectToEnrollmentApiKey(so);
}
/**
* Invalidate an api key and mark it as inactive
* @param soClient
* @param id
*/
export async function deleteEnrollmentApiKey(soClient: SavedObjectsClientContract, id: string) {
const enrollmentApiKey = await getEnrollmentAPIKey(soClient, id);
await invalidateAPIKeys(soClient, [enrollmentApiKey.api_key_id]);
await soClient.update(ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE, id, {
active: false,
});
}
export async function deleteEnrollmentApiKeyForAgentPolicyId(
soClient: SavedObjectsClientContract,
agentPolicyId: string
) {
let hasMore = true;
let page = 1;
while (hasMore) {
const { items } = await listEnrollmentApiKeys(soClient, {
page: page++,
perPage: 100,
kuery: `${ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE}.policy_id:${agentPolicyId}`,
});
if (items.length === 0) {
hasMore = false;
}
for (const apiKey of items) {
await deleteEnrollmentApiKey(soClient, apiKey.id);
}
}
}
export async function generateEnrollmentAPIKey(
soClient: SavedObjectsClientContract,
data: {
name?: string;
expiration?: string;
agentPolicyId?: string;
}
) {
const id = uuid.v4();
const { name: providedKeyName } = data;
if (data.agentPolicyId) {
await validateAgentPolicyId(soClient, data.agentPolicyId);
}
const agentPolicyId =
data.agentPolicyId ?? (await agentPolicyService.getDefaultAgentPolicyId(soClient));
const name = providedKeyName ? `${providedKeyName} (${id})` : id;
const key = await createAPIKey(soClient, name, {
// Useless role to avoid to have the privilege of the user that created the key
'fleet-apikey-enroll': {
cluster: [],
applications: [
{
application: '.fleet',
privileges: ['no-privileges'],
resources: ['*'],
},
],
},
});
if (!key) {
throw new Error('Unable to create an enrollment api key');
}
const apiKey = Buffer.from(`${key.id}:${key.api_key}`).toString('base64');
const so = await soClient.create<EnrollmentAPIKeySOAttributes>(
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
{
active: true,
api_key_id: key.id,
api_key: apiKey,
name,
policy_id: agentPolicyId,
created_at: new Date().toISOString(),
}
);
return getEnrollmentAPIKey(soClient, so.id);
}
async function validateAgentPolicyId(soClient: SavedObjectsClientContract, agentPolicyId: string) {
try {
await agentPolicyService.get(soClient, agentPolicyId);
} catch (e) {
if (e.isBoom && e.output.statusCode === 404) {
throw Boom.badRequest(`Agent policy ${agentPolicyId} does not exist`);
}
throw e;
}
}
export async function getEnrollmentAPIKeyById(
soClient: SavedObjectsClientContract,
apiKeyId: string
) {
const [enrollmentAPIKey] = (
await soClient.find<EnrollmentAPIKeySOAttributes>({
type: ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
searchFields: ['api_key_id'],
search: escapeSearchQueryPhrase(apiKeyId),
})
).saved_objects.map(savedObjectToEnrollmentApiKey);
if (enrollmentAPIKey?.api_key_id !== apiKeyId) {
throw new Error('find enrollmentKeyById returned an incorrect key');
}
return enrollmentAPIKey;
}
function savedObjectToEnrollmentApiKey({
error,
attributes,

View file

@ -51,18 +51,13 @@ export interface AgentService {
* Authenticate an agent with access toekn
*/
authenticateAgentWithAccessToken(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
request: KibanaRequest
): Promise<Agent>;
/**
* Return the status by the Agent's id
*/
getAgentStatusById(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentId: string
): Promise<AgentStatus>;
getAgentStatusById(esClient: ElasticsearchClient, agentId: string): Promise<AgentStatus>;
/**
* List agents
*/

View file

@ -34,7 +34,6 @@ import { settingsService } from '.';
import { awaitIfPending } from './setup_utils';
import { createDefaultSettings } from './settings';
import { ensureAgentActionPolicyChangeExists } from './agents';
import { appContextService } from './app_context';
import { awaitIfFleetServerSetupPending } from './fleet_server';
const FLEET_ENROLL_USERNAME = 'fleet_enroll';
@ -57,7 +56,6 @@ async function createSetupSideEffects(
esClient: ElasticsearchClient,
callCluster: CallESAsCurrentUser
): Promise<SetupStatus> {
const isFleetServerEnabled = appContextService.getConfig()?.agents.fleetServerEnabled;
const [
installedPackages,
defaultOutput,
@ -68,9 +66,7 @@ async function createSetupSideEffects(
ensureInstalledDefaultPackages(soClient, callCluster),
outputService.ensureDefaultOutput(soClient),
agentPolicyService.ensureDefaultAgentPolicy(soClient, esClient),
isFleetServerEnabled
? agentPolicyService.ensureDefaultFleetServerAgentPolicy(soClient, esClient)
: {},
agentPolicyService.ensureDefaultFleetServerAgentPolicy(soClient, esClient),
updateFleetRoleIfExists(callCluster),
settingsService.getSettings(soClient).catch((e: any) => {
if (e.isBoom && e.output.statusCode === 404) {
@ -90,25 +86,23 @@ async function createSetupSideEffects(
// packages that are stuck in the installing state.
await ensurePackagesCompletedInstall(soClient, callCluster);
if (isFleetServerEnabled) {
await awaitIfFleetServerSetupPending();
await awaitIfFleetServerSetupPending();
const fleetServerPackage = await ensureInstalledPackage({
savedObjectsClient: soClient,
pkgName: FLEET_SERVER_PACKAGE,
const fleetServerPackage = await ensureInstalledPackage({
savedObjectsClient: soClient,
pkgName: FLEET_SERVER_PACKAGE,
callCluster,
});
if (defaultFleetServerPolicyCreated) {
await addPackageToAgentPolicy(
soClient,
esClient,
callCluster,
});
if (defaultFleetServerPolicyCreated) {
await addPackageToAgentPolicy(
soClient,
esClient,
callCluster,
fleetServerPackage,
defaultFleetServerPolicy,
defaultOutput
);
}
fleetServerPackage,
defaultFleetServerPolicy,
defaultOutput
);
}
// If we just created the default fleet server policy add the fleet server package

View file

@ -310,7 +310,7 @@ const fleetEnrollAgentForHost = async (
path: ENROLLMENT_API_KEY_ROUTES.LIST_PATTERN,
method: 'GET',
query: {
kuery: `fleet-enrollment-api-keys.policy_id:"${agentPolicyId}"`,
kuery: `policy_id:"${agentPolicyId}"`,
},
})
.then((apiKeysResponse) => {

View file

@ -618,12 +618,12 @@ export const EndpointList = () => {
<LinkToApp
appId="fleet"
appPath={`#${pagePathGetters.fleet_agent_list({
kuery: 'fleet-agents.packages : "endpoint"',
kuery: 'packages : "endpoint"',
})}`}
href={`${services?.application?.getUrlForApp(
'fleet'
)}#${pagePathGetters.fleet_agent_list({
kuery: 'fleet-agents.packages : "endpoint"',
kuery: 'packages : "endpoint"',
})}`}
>
<FormattedMessage

View file

@ -148,7 +148,7 @@ export const sendGetFleetAgentsWithEndpoint = (
query: {
page: 1,
perPage: 1,
kuery: 'fleet-agents.packages : "endpoint"',
kuery: 'packages : "endpoint"',
},
});
};

View file

@ -71,23 +71,22 @@ const expectedEndpointExceptions: WrappedTranslatedExceptionList = {
},
],
};
const mockIngestSOResponse = {
page: 1,
per_page: 100,
total: 1,
saved_objects: [
{
id: 'agent1',
type: 'agent',
references: [],
score: 0,
attributes: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
const mockFleetESResponse = {
body: {
hits: {
hits: [
{
_id: 'agent1',
_source: {
active: true,
access_api_key_id: 'pedTuHIBTEDt93wW0Fhr',
},
},
],
},
],
},
};
const AuthHeader = 'ApiKey cGVkVHVISUJURUR0OTN3VzBGaHI6TnU1U0JtbHJSeC12Rm9qQWpoSHlUZw==';
describe('test alerts route', () => {
@ -101,6 +100,7 @@ describe('test alerts route', () => {
let endpointAppContextService: EndpointAppContextService;
let cache: LRU<string, Buffer>;
let ingestSavedObjectClient: jest.Mocked<SavedObjectsClientContract>;
let esClientMock: ReturnType<typeof elasticsearchServiceMock.createInternalClient>;
beforeEach(() => {
mockClusterClient = elasticsearchServiceMock.createLegacyClusterClient();
@ -113,9 +113,12 @@ describe('test alerts route', () => {
cache = new LRU<string, Buffer>({ max: 10, maxAge: 1000 * 60 * 60 });
const startContract = createMockEndpointAppContextServiceStartContract();
// The authentication with the Fleet Plugin needs a separate scoped SO Client
// // The authentication with the Fleet Plugin needs a separate scoped ES CLient
esClientMock = elasticsearchServiceMock.createInternalClient();
// @ts-expect-error
esClientMock.search.mockResolvedValue(mockFleetESResponse);
ingestSavedObjectClient = savedObjectsClientMock.create();
ingestSavedObjectClient.find.mockReturnValue(Promise.resolve(mockIngestSOResponse));
(startContract.savedObjectsStart.getScopedClient as jest.Mock).mockReturnValue(
ingestSavedObjectClient
);
@ -175,7 +178,7 @@ describe('test alerts route', () => {
client: mockSavedObjectClient,
},
elasticsearch: {
client: { asInternalUser: elasticsearchServiceMock.createInternalClient() },
client: { asInternalUser: esClientMock },
},
},
} as unknown) as SecuritySolutionRequestHandlerContext,
@ -222,7 +225,7 @@ describe('test alerts route', () => {
client: mockSavedObjectClient,
},
elasticsearch: {
client: { asInternalUser: elasticsearchServiceMock.createInternalClient() },
client: { asInternalUser: esClientMock },
},
},
} as unknown) as SecuritySolutionRequestHandlerContext,
@ -259,7 +262,7 @@ describe('test alerts route', () => {
client: mockSavedObjectClient,
},
elasticsearch: {
client: { asInternalUser: elasticsearchServiceMock.createInternalClient() },
client: { asInternalUser: esClientMock },
},
},
} as unknown) as SecuritySolutionRequestHandlerContext,
@ -290,7 +293,7 @@ describe('test alerts route', () => {
client: mockSavedObjectClient,
},
elasticsearch: {
client: { asInternalUser: elasticsearchServiceMock.createInternalClient() },
client: { asInternalUser: esClientMock },
},
},
} as unknown) as SecuritySolutionRequestHandlerContext,
@ -312,9 +315,8 @@ describe('test alerts route', () => {
});
// Mock the SavedObjectsClient find response for verifying the API token with no results
mockIngestSOResponse.saved_objects = [];
mockIngestSOResponse.total = 0;
ingestSavedObjectClient.find.mockReturnValue(Promise.resolve(mockIngestSOResponse));
// @ts-expect-error
esClientMock.search.mockResolvedValue({ body: { hits: { hits: [] } } });
[routeConfig, routeHandler] = routerMock.get.mock.calls.find(([{ path }]) =>
path.startsWith('/api/endpoint/artifacts/download')
@ -327,7 +329,7 @@ describe('test alerts route', () => {
client: mockSavedObjectClient,
},
elasticsearch: {
client: { asInternalUser: elasticsearchServiceMock.createInternalClient() },
client: { asInternalUser: esClientMock },
},
},
} as unknown) as SecuritySolutionRequestHandlerContext,

View file

@ -55,7 +55,6 @@ export function registerDownloadArtifactRoute(
try {
scopedSOClient = endpointContext.service.getScopedSavedObjectsClient(req);
await authenticateAgentWithAccessToken(
scopedSOClient,
context.core.elasticsearch.client.asInternalUser,
req
);

View file

@ -19,6 +19,7 @@ import type { SecuritySolutionRequestHandlerContext } from '../../../types';
import { getESQueryHostMetadataByID, kibanaRequestToMetadataListESQuery } from './query_builders';
import { Agent, AgentStatus, PackagePolicy } from '../../../../../fleet/common/types/models';
import { AgentNotFoundError } from '../../../../../fleet/server';
import { EndpointAppContext, HostListQueryResult } from '../../types';
import { GetMetadataListRequestSchema, GetMetadataRequestSchema } from './index';
import { findAllUnenrolledAgentIds } from './support/unenroll';
@ -202,16 +203,11 @@ async function findAgent(
return await metadataRequestContext.endpointAppContextService
?.getAgentService()
?.getAgent(
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
hostMetadata.elastic.agent.id
);
} catch (e) {
if (
metadataRequestContext.requestHandlerContext.core.savedObjects.client.errors.isNotFoundError(
e
)
) {
if (e instanceof AgentNotFoundError) {
metadataRequestContext.logger.warn(
`agent with id ${hostMetadata.elastic.agent.id} not found`
);
@ -277,17 +273,12 @@ export async function enrichHostMetadata(
const status = await metadataRequestContext.endpointAppContextService
?.getAgentService()
?.getAgentStatusById(
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
elasticAgentId
);
hostStatus = HOST_STATUS_MAPPING.get(status!) || HostStatus.ERROR;
} catch (e) {
if (
metadataRequestContext.requestHandlerContext.core.savedObjects.client.errors.isNotFoundError(
e
)
) {
if (e instanceof AgentNotFoundError) {
log.warn(`agent with id ${elasticAgentId} not found`);
} else {
log.error(e);
@ -300,7 +291,6 @@ export async function enrichHostMetadata(
const agent = await metadataRequestContext.endpointAppContextService
?.getAgentService()
?.getAgent(
metadataRequestContext.requestHandlerContext.core.savedObjects.client,
metadataRequestContext.requestHandlerContext.core.elasticsearch.client.asCurrentUser,
elasticAgentId
);

View file

@ -71,7 +71,7 @@ describe('test filtering endpoint hosts by agent status', () => {
['offline']
);
const offlineKuery = AgentStatusKueryHelper.buildKueryForOfflineAgents();
expect(mockAgentService.listAgents.mock.calls[0][2].kuery).toEqual(
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
expect.stringContaining(offlineKuery)
);
expect(result).toBeDefined();
@ -105,7 +105,7 @@ describe('test filtering endpoint hosts by agent status', () => {
);
const unenrollKuery = AgentStatusKueryHelper.buildKueryForUnenrollingAgents();
const errorKuery = AgentStatusKueryHelper.buildKueryForErrorAgents();
expect(mockAgentService.listAgents.mock.calls[0][2].kuery).toEqual(
expect(mockAgentService.listAgents.mock.calls[0][1].kuery).toEqual(
expect.stringContaining(`${unenrollKuery} OR ${errorKuery}`)
);
expect(result).toBeDefined();

View file

@ -31,7 +31,7 @@ export async function findAgentIDsByStatus(
page: pageNum,
perPage: pageSize,
showInactive: true,
kuery: `(fleet-agents.packages : "endpoint" AND (${helpers.join(' OR ')}))`,
kuery: `(packages : "endpoint" AND (${helpers.join(' OR ')}))`,
};
};
@ -41,7 +41,7 @@ export async function findAgentIDsByStatus(
let hasMore = true;
while (hasMore) {
const agents = await agentService.listAgents(soClient, esClient, searchOptions(page++));
const agents = await agentService.listAgents(esClient, searchOptions(page++));
result.push(...agents.agents.map((agent: Agent) => agent.id));
hasMore = agents.agents.length > 0;
}

View file

@ -20,8 +20,7 @@ export async function findAllUnenrolledAgentIds(
page: pageNum,
perPage: pageSize,
showInactive: true,
kuery:
'(fleet-agents.active : false) OR (NOT fleet-agents.packages : "endpoint" AND fleet-agents.active : true)',
kuery: '(active : false) OR (NOT packages : "endpoint" AND active : true)',
};
};
@ -31,11 +30,7 @@ export async function findAllUnenrolledAgentIds(
let hasMore = true;
while (hasMore) {
const unenrolledAgents = await agentService.listAgents(
soClient,
esClient,
searchOptions(page++)
);
const unenrolledAgents = await agentService.listAgents(esClient, searchOptions(page++));
result.push(...unenrolledAgents.agents.map((agent: Agent) => agent.id));
hasMore = unenrolledAgents.agents.length > 0;
}

View file

@ -15,7 +15,6 @@ import { GetHostPolicyResponse, HostPolicyResponse } from '../../../../common/en
import { INITIAL_POLICY_ID } from './index';
import { Agent } from '../../../../../fleet/common/types/models';
import { EndpointAppContext } from '../../types';
import { AGENT_SAVED_OBJECT_TYPE } from '../../../../../fleet/common/constants';
export function getESQueryPolicyResponseByAgentID(agentID: string, index: string) {
return {
@ -83,14 +82,14 @@ export async function getAgentPolicySummary(
policyId?: string,
pageSize: number = 1000
): Promise<{ [key: string]: number }> {
const agentQuery = `${AGENT_SAVED_OBJECT_TYPE}.packages:"${packageName}"`;
const agentQuery = `packages:"${packageName}"`;
if (policyId) {
return transformAgentVersionMap(
await agentVersionsMap(
endpointAppContext,
soClient,
esClient,
`${agentQuery} AND ${AGENT_SAVED_OBJECT_TYPE}.policy_id:${policyId}`,
`${agentQuery} AND policy_id:${policyId}`,
pageSize
)
);
@ -123,7 +122,7 @@ export async function agentVersionsMap(
while (hasMore) {
const queryResult = await endpointAppContext.service
.getAgentService()!
.listAgents(soClient, esClient, searchOptions(page++));
.listAgents(esClient, searchOptions(page++));
queryResult.agents.forEach((agent: Agent) => {
const agentVersion = agent.local_metadata?.elastic?.agent?.version;
if (result.has(agentVersion)) {

View file

@ -13,19 +13,13 @@ export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
describe('fleet_agent_policies', () => {
const createdPolicyIds: string[] = [];
after(async () => {
const deletedPromises = createdPolicyIds.map((agentPolicyId) =>
supertest
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId })
.expect(200)
);
await Promise.all(deletedPromises);
});
describe('POST /api/fleet/agent_policies', () => {
before(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
after(async () => {
await esArchiver.unload('fleet/empty_fleet_server');
});
it('should work with valid minimum required values', async () => {
const {
body: { item: createdPolicy },
@ -201,6 +195,23 @@ export default function ({ getService }: FtrProviderContext) {
});
describe('PUT /api/fleet/agent_policies/{agentPolicyId}', () => {
before(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
const createdPolicyIds: string[] = [];
after(async () => {
const deletedPromises = createdPolicyIds.map((agentPolicyId) =>
supertest
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId })
.expect(200)
);
await Promise.all(deletedPromises);
});
after(async () => {
await esArchiver.unload('fleet/empty_fleet_server');
});
let agentPolicyId: undefined | string;
it('should work with valid values', async () => {
const {
@ -310,6 +321,12 @@ export default function ({ getService }: FtrProviderContext) {
});
describe('POST /api/fleet/agent_policies/delete', () => {
before(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
after(async () => {
await esArchiver.unload('fleet/empty_fleet_server');
});
let managedPolicy: any | undefined;
it('should prevent managed policies being deleted', async () => {
const {

View file

@ -9,6 +9,7 @@ import expect from '@kbn/expect';
import { skipIfNoDockerRegistry } from '../../helpers';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { setupFleetAndAgents, getSupertestWithoutAuth } from '../agents/services';
import { AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS } from '../../../../plugins/fleet/common';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
@ -75,6 +76,10 @@ export default function (providerContext: FtrProviderContext) {
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
});
after(async () => {
// Wait before agent status is updated
return new Promise((resolve) => setTimeout(resolve, AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS));
});
after(async () => {
await esArchiver.unload('fleet/agents');
});

View file

@ -14,7 +14,6 @@ export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const esArchiver = getService('esArchiver');
const esClient = getService('es');
const kibanaServer = getService('kibanaServer');
const supertestWithoutAuth = getSupertestWithoutAuth(providerContext);
const supertest = getService('supertest');
@ -33,13 +32,13 @@ export default function (providerContext: FtrProviderContext) {
const {
body: { _source: agentDoc },
} = await esClient.get({
index: '.kibana',
id: 'fleet-agents:agent1',
index: '.fleet-agents',
id: 'agent1',
});
agentDoc['fleet-agents'].access_api_key_id = apiKey.id;
agentDoc.access_api_key_id = apiKey.id;
await esClient.update({
index: '.kibana',
id: 'fleet-agents:agent1',
index: '.fleet-agents',
id: 'agent1',
refresh: true,
body: {
doc: agentDoc,
@ -240,11 +239,11 @@ export default function (providerContext: FtrProviderContext) {
})
.expect(200);
const res = await kibanaServer.savedObjects.get({
type: 'fleet-agents',
const res = await esClient.get({
index: '.fleet-agents',
id: 'agent1',
});
expect(res.attributes.upgraded_at).to.be.ok();
expect(res.body._source.upgraded_at).to.be.ok();
});
});
}

View file

@ -15,7 +15,7 @@ export default function (providerContext: FtrProviderContext) {
describe('fleet_agents_actions', () => {
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
await esArchiver.load('fleet/agents');
});
after(async () => {
await esArchiver.unload('fleet/agents');
@ -86,7 +86,7 @@ export default function (providerContext: FtrProviderContext) {
});
it('should return a 404 when agent does not exist', async () => {
const { body: apiResponse } = await supertest
await supertest
.post(`/api/fleet/agents/agent100/actions`)
.set('kbn-xsrf', 'xx')
.send({
@ -96,7 +96,6 @@ export default function (providerContext: FtrProviderContext) {
},
})
.expect(404);
expect(apiResponse.message).to.eql('Saved object [fleet-agents/agent100] not found');
});
});
}

View file

@ -11,6 +11,7 @@ import uuid from 'uuid';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { getSupertestWithoutAuth, setupFleetAndAgents } from './services';
import { skipIfNoDockerRegistry } from '../../helpers';
import { AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS } from '../../../../plugins/fleet/common';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
@ -34,13 +35,13 @@ export default function (providerContext: FtrProviderContext) {
const {
body: { _source: agentDoc },
} = await esClient.get({
index: '.kibana',
id: 'fleet-agents:agent1',
index: '.fleet-agents',
id: 'agent1',
});
agentDoc['fleet-agents'].access_api_key_id = apiKey.id;
agentDoc.access_api_key_id = apiKey.id;
await esClient.update({
index: '.kibana',
id: 'fleet-agents:agent1',
index: '.fleet-agents',
id: 'agent1',
refresh: true,
body: {
doc: agentDoc,
@ -48,6 +49,10 @@ export default function (providerContext: FtrProviderContext) {
});
});
setupFleetAndAgents(providerContext);
after(async () => {
// Wait before agent status is updated
return new Promise((resolve) => setTimeout(resolve, AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS));
});
after(async () => {
await esArchiver.unload('fleet/agents');
});

View file

@ -10,6 +10,7 @@ import expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { setupFleetAndAgents, getSupertestWithoutAuth } from './services';
import { skipIfNoDockerRegistry } from '../../helpers';
import { AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS } from '../../../../plugins/fleet/common';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
@ -23,25 +24,38 @@ export default function (providerContext: FtrProviderContext) {
describe('fleet_agent_flow', () => {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('fleet/empty_fleet_server');
});
setupFleetAndAgents(providerContext);
after(async () => {
await esArchiver.unload('empty_kibana');
// Wait before agent status is updated
return new Promise((resolve) => setTimeout(resolve, AGENT_UPDATE_LAST_CHECKIN_INTERVAL_MS));
});
after(async () => {
await esArchiver.unload('fleet/empty_fleet_server');
});
it('should work', async () => {
const kibanaVersionAccessor = kibanaServer.version;
const kibanaVersion = await kibanaVersionAccessor.get();
const { body: policiesRes } = await supertest.get(`/api/fleet/agent_policies`).expect(200);
expect(policiesRes.items).length(2);
const { id: defaultPolicyId } = policiesRes.items.find((p: any) => p.is_default);
// Get enrollment token
const { body: enrollmentApiKeysResponse } = await supertest
.get(`/api/fleet/enrollment-api-keys`)
.expect(200);
expect(enrollmentApiKeysResponse.list).length(1);
expect(enrollmentApiKeysResponse.list).length(2);
const { id: enrollmentKeyId } = enrollmentApiKeysResponse.list.find(
(key: any) => key.policy_id === defaultPolicyId
);
const { body: enrollmentApiKeyResponse } = await supertest
.get(`/api/fleet/enrollment-api-keys/${enrollmentApiKeysResponse.list[0].id}`)
.get(`/api/fleet/enrollment-api-keys/${enrollmentKeyId}`)
.expect(200);
expect(enrollmentApiKeyResponse.item).to.have.key('api_key');
@ -194,14 +208,24 @@ export default function (providerContext: FtrProviderContext) {
const kibanaVersionAccessor = kibanaServer.version;
const kibanaVersion = await kibanaVersionAccessor.get();
// Get enrollment token
const { body: policiesRes } = await supertest.get(`/api/fleet/agent_policies`).expect(200);
expect(policiesRes.items).length(2);
const { id: defaultPolicyId } = policiesRes.items.find((p: any) => p.is_default);
// Get enrollment token
const { body: enrollmentApiKeysResponse } = await supertest
.get(`/api/fleet/enrollment-api-keys`)
.expect(200);
expect(enrollmentApiKeysResponse.list).length(1);
expect(enrollmentApiKeysResponse.list).length(2);
const { id: enrollmentKeyId } = enrollmentApiKeysResponse.list.find(
(key: any) => key.policy_id === defaultPolicyId
);
const { body: enrollmentApiKeyResponse } = await supertest
.get(`/api/fleet/enrollment-api-keys/${enrollmentApiKeysResponse.list[0].id}`)
.get(`/api/fleet/enrollment-api-keys/${enrollmentKeyId}`)
.expect(200);
expect(enrollmentApiKeyResponse.item).to.have.key('api_key');

View file

@ -58,7 +58,7 @@ export default function ({ getService }: FtrProviderContext) {
}
}
await esArchiver.loadIfNeeded('fleet/agents');
await esArchiver.load('fleet/agents');
});
after(async () => {
await esArchiver.unload('fleet/agents');

View file

@ -27,7 +27,7 @@ export default function (providerContext: FtrProviderContext) {
describe('fleet_agents_enroll', () => {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
await esArchiver.load('fleet/agents');
const { body: apiKeyBody } = await esClient.security.createApiKey<typeof apiKey>({
body: {
@ -38,14 +38,14 @@ export default function (providerContext: FtrProviderContext) {
const {
body: { _source: enrollmentApiKeyDoc },
} = await esClient.get({
index: '.kibana',
id: 'fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0',
index: '.fleet-enrollment-api-keys',
id: 'ed22ca17-e178-4cfe-8b02-54ea29fbd6d0',
});
// @ts-ignore
enrollmentApiKeyDoc['fleet-enrollment-api-keys'].api_key_id = apiKey.id;
enrollmentApiKeyDoc.api_key_id = apiKey.id;
await esClient.update({
index: '.kibana',
id: 'fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0',
index: '.fleet-enrollment-api-keys',
id: 'ed22ca17-e178-4cfe-8b02-54ea29fbd6d0',
refresh: true,
body: {
doc: enrollmentApiKeyDoc,

View file

@ -1,56 +0,0 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
export default function ({ getService }: FtrProviderContext) {
const esArchiver = getService('esArchiver');
const supertest = getService('supertest');
describe('fleet_agents_events', () => {
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
});
after(async () => {
await esArchiver.unload('fleet/agents');
});
it('should return a 200 and the events for a given agent', async () => {
const { body: apiResponse } = await supertest
.get(`/api/fleet/agents/agent1/events`)
.expect(200);
expect(apiResponse).to.have.keys(['list', 'total', 'page']);
expect(apiResponse.total).to.be(2);
expect(apiResponse.page).to.be(1);
const event = apiResponse.list[0];
expect(event).to.have.keys('type', 'subtype', 'message', 'payload');
expect(event.payload).to.have.keys('previous_state');
});
it('should return a 400 when given an invalid "kuery" value', async () => {
await supertest
.get(`/api/fleet/agents/agent1/events?kuery=m`) // missing saved object type
.expect(400);
});
it('should accept a valid "kuery" value', async () => {
const filter = encodeURIComponent('fleet-agent-events.subtype : "STOPPED"');
const { body: apiResponse } = await supertest
.get(`/api/fleet/agents/agent1/events?kuery=${filter}`)
.expect(200);
expect(apiResponse).to.have.keys(['list', 'total', 'page']);
expect(apiResponse.total).to.be(1);
expect(apiResponse.page).to.be(1);
const event = apiResponse.list[0];
expect(event.subtype).to.eql('STOPPED');
});
});
}

View file

@ -100,9 +100,7 @@ export default function ({ getService }: FtrProviderContext) {
});
it('should return a 400 when given an invalid "kuery" value', async () => {
await supertest
.get(`/api/fleet/agents?kuery=m`) // missing saved object type
.expect(400);
await supertest.get(`/api/fleet/agents?kuery=.test%3A`).expect(400);
});
it('should accept a valid "kuery" value', async () => {
const filter = encodeURIComponent('fleet-agents.access_api_key_id : "api-key-2"');

View file

@ -15,13 +15,19 @@ export default function (providerContext: FtrProviderContext) {
const supertest = getService('supertest');
describe('fleet_reassign_agent', () => {
setupFleetAndAgents(providerContext);
beforeEach(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
before(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
beforeEach(async () => {
await esArchiver.load('fleet/agents');
});
setupFleetAndAgents(providerContext);
afterEach(async () => {
await esArchiver.unload('fleet/agents');
});
after(async () => {
await esArchiver.unload('fleet/empty_fleet_server');
});
it('should allow to reassign single agent', async () => {
await supertest

View file

@ -23,7 +23,7 @@ export default function (providerContext: FtrProviderContext) {
let accessAPIKeyId: string;
let outputAPIKeyId: string;
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
await esArchiver.load('fleet/agents');
});
setupFleetAndAgents(providerContext);
beforeEach(async () => {
@ -42,19 +42,19 @@ export default function (providerContext: FtrProviderContext) {
const {
body: { _source: agentDoc },
} = await esClient.get({
index: '.kibana',
id: 'fleet-agents:agent1',
index: '.fleet-agents',
id: 'agent1',
});
// @ts-ignore
agentDoc['fleet-agents'].access_api_key_id = accessAPIKeyId;
agentDoc['fleet-agents'].default_api_key_id = outputAPIKeyBody.id;
agentDoc['fleet-agents'].default_api_key = Buffer.from(
agentDoc.access_api_key_id = accessAPIKeyId;
agentDoc.default_api_key_id = outputAPIKeyBody.id;
agentDoc.default_api_key = Buffer.from(
`${outputAPIKeyBody.id}:${outputAPIKeyBody.api_key}`
).toString('base64');
await esClient.update({
index: '.kibana',
id: 'fleet-agents:agent1',
index: '.fleet-agents',
id: 'agent1',
refresh: true,
body: {
doc: agentDoc,

View file

@ -10,7 +10,7 @@ import semver from 'semver';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { setupFleetAndAgents } from './services';
import { skipIfNoDockerRegistry } from '../../helpers';
import { AGENT_SAVED_OBJECT_TYPE } from '../../../../plugins/fleet/common';
import { AGENTS_INDEX } from '../../../../plugins/fleet/common';
const makeSnapshotVersion = (version: string) => {
return version.endsWith('-SNAPSHOT') ? version : `${version}-SNAPSHOT`;
@ -19,26 +19,33 @@ const makeSnapshotVersion = (version: string) => {
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const es = getService('es');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
describe('fleet upgrade agent', () => {
skipIfNoDockerRegistry(providerContext);
setupFleetAndAgents(providerContext);
beforeEach(async () => {
before(async () => {
await esArchiver.loadIfNeeded('fleet/agents');
});
afterEach(async () => {
setupFleetAndAgents(providerContext);
beforeEach(async () => {
await esArchiver.load('fleet/agents');
});
after(async () => {
await esArchiver.unload('fleet/agents');
});
it('should respond 200 to upgrade agent and update the agent SO', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await supertest
@ -56,11 +63,14 @@ export default function (providerContext: FtrProviderContext) {
it('should respond 400 if upgrading agent with version the same as snapshot version', async () => {
const kibanaVersion = await kibanaServer.version.get();
const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion);
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } },
},
},
});
await supertest
@ -74,11 +84,14 @@ export default function (providerContext: FtrProviderContext) {
it('should respond 200 if upgrading agent with version the same as snapshot version and force flag is passed', async () => {
const kibanaVersion = await kibanaServer.version.get();
const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion);
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: kibanaVersion } } },
},
},
});
await supertest
@ -94,11 +107,14 @@ export default function (providerContext: FtrProviderContext) {
const kibanaVersion = await kibanaServer.version.get();
const kibanaVersionSnapshot = makeSnapshotVersion(kibanaVersion);
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await supertest
@ -111,11 +127,14 @@ export default function (providerContext: FtrProviderContext) {
});
it('should respond 200 to upgrade agent and update the agent SO without source_uri', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await supertest
@ -156,10 +175,15 @@ export default function (providerContext: FtrProviderContext) {
});
it('should respond 400 if trying to upgrade an agent that is unenrolled', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: { unenrolled_at: new Date().toISOString() },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
unenrolled_at: new Date().toISOString(),
},
},
});
await supertest
.post(`/api/fleet/agents/agent1/upgrade`)
@ -184,19 +208,27 @@ export default function (providerContext: FtrProviderContext) {
it('should respond 200 to bulk upgrade upgradeable agents and update the agent SOs', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: {
elastic: {
agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') },
},
},
},
},
});
@ -219,19 +251,27 @@ export default function (providerContext: FtrProviderContext) {
it('should allow to upgrade multiple upgradeable agents by kuery', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: {
elastic: {
agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') },
},
},
},
},
});
@ -239,7 +279,7 @@ export default function (providerContext: FtrProviderContext) {
.post(`/api/fleet/agents/bulk_upgrade`)
.set('kbn-xsrf', 'xxx')
.send({
agents: 'fleet-agents.active: true',
agents: 'active:true',
version: kibanaVersion,
})
.expect(200);
@ -256,19 +296,23 @@ export default function (providerContext: FtrProviderContext) {
await supertest.post(`/api/fleet/agents/agent1/unenroll`).set('kbn-xsrf', 'xxx').send({
force: true,
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: '0.0.0' } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
@ -288,20 +332,26 @@ export default function (providerContext: FtrProviderContext) {
});
it('should not upgrade an unenrolled agent during bulk_upgrade', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
unenrolled_at: new Date().toISOString(),
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
unenrolled_at: new Date().toISOString(),
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: '0.0.0' } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: '0.0.0' } },
},
},
},
});
@ -321,27 +371,38 @@ export default function (providerContext: FtrProviderContext) {
});
it('should not upgrade an non upgradeable agent during bulk_upgrade', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
});
await kibanaServer.savedObjects.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: {
elastic: {
agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') },
},
},
},
},
});
await es.update({
id: 'agent3',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } },
},
},
});
await supertest
@ -362,27 +423,38 @@ export default function (providerContext: FtrProviderContext) {
});
it('should upgrade a non upgradeable agent during bulk_upgrade with force flag', async () => {
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
});
await kibanaServer.savedObjects.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: { agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: {
elastic: {
agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') },
},
},
},
},
});
await es.update({
id: 'agent3',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: false, version: '0.0.0' } } },
},
},
});
await supertest
@ -403,18 +475,24 @@ export default function (providerContext: FtrProviderContext) {
expect(typeof agent3data.body.item.upgrade_started_at).to.be('string');
});
it('should respond 400 if trying to bulk upgrade to a version that does not match installed kibana version', async () => {
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await supertest
@ -438,25 +516,30 @@ export default function (providerContext: FtrProviderContext) {
});
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent2',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: {
elastic: {
agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: {
elastic: {
agent: { upgradeable: true, version: semver.inc(kibanaVersion, 'patch') },
},
},
},
},
});
// attempt to upgrade agent in managed policy
const { body } = await supertest
.post(`/api/fleet/agents/bulk_upgrade`)
@ -486,14 +569,16 @@ export default function (providerContext: FtrProviderContext) {
});
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
await es.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
refresh: 'wait_for',
index: AGENTS_INDEX,
body: {
doc: {
local_metadata: { elastic: { agent: { upgradeable: true, version: '0.0.0' } } },
},
},
});
// attempt to upgrade agent in managed policy
const { body } = await supertest
.post(`/api/fleet/agents/agent1/upgrade`)

View file

@ -13,9 +13,20 @@ export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const es = getService('es');
const esArchiver = getService('esArchiver');
describe('fleet_agents_setup', () => {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('fleet/empty_fleet_server');
});
after(async () => {
await esArchiver.unload('empty_kibana');
await esArchiver.load('fleet/empty_fleet_server');
});
beforeEach(async () => {
try {
await es.security.deleteUser({

View file

@ -37,7 +37,7 @@ export default function (providerContext: FtrProviderContext) {
.get(`/api/fleet/enrollment-api-keys`)
.expect(200);
expect(apiResponse.total).to.be(3);
expect(apiResponse.total).to.be(4);
expect(apiResponse.list[0]).to.have.keys('id', 'api_key_id', 'name');
});
});

View file

@ -13,6 +13,7 @@ import { setupFleetAndAgents } from '../agents/services';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
// use function () {} and not () => {} here
// because `this` has to point to the Mocha context
@ -20,7 +21,13 @@ export default function (providerContext: FtrProviderContext) {
describe('EPM - list', async function () {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
setupFleetAndAgents(providerContext);
after(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
describe('list api tests', async () => {
it('lists all packages from the registry', async function () {
@ -44,6 +51,7 @@ export default function (providerContext: FtrProviderContext) {
return response.body;
};
const listResponse = await fetchLimitedPackageList();
expect(listResponse.response).to.eql(['endpoint']);
});
});

View file

@ -1,3 +1,5 @@
package_paths:
- /packages/production
- /packages/test-packages
package_paths:
- /packages/production
# TODO remove temp
- /packages/snapshot
- /packages/test-packages

View file

@ -13,9 +13,19 @@ export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const es = getService('es');
const esArchiver = getService('esArchiver');
describe('fleet_setup', () => {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('empty_kibana');
await esArchiver.load('fleet/empty_fleet_server');
});
after(async () => {
await esArchiver.unload('empty_kibana');
await esArchiver.load('fleet/empty_fleet_server');
});
beforeEach(async () => {
try {
await es.security.deleteUser({
@ -117,7 +127,7 @@ export default function (providerContext: FtrProviderContext) {
.map((p: any) => p.name)
.sort();
expect(installedPackages).to.eql(['elastic_agent', 'endpoint', 'system']);
expect(installedPackages).to.eql(['elastic_agent', 'endpoint', 'fleet_server', 'system']);
});
});
}

View file

@ -20,7 +20,6 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./agents/enroll'));
loadTestFile(require.resolve('./agents/unenroll'));
loadTestFile(require.resolve('./agents/checkin'));
loadTestFile(require.resolve('./agents/events'));
loadTestFile(require.resolve('./agents/acks'));
loadTestFile(require.resolve('./agents/complete_flow'));
loadTestFile(require.resolve('./agents/actions'));

View file

@ -20,6 +20,14 @@ export default function ({ getService }: FtrProviderContext) {
describe('Package Policy - create', async function () {
let agentPolicyId: string;
before(async () => {
await getService('esArchiver').load('empty_kibana');
await getService('esArchiver').load('fleet/empty_fleet_server');
});
after(async () => {
await getService('esArchiver').unload('empty_kibana');
await getService('esArchiver').unload('fleet/empty_fleet_server');
});
before(async function () {
const { body: agentPolicyResponse } = await supertest

View file

@ -20,7 +20,10 @@ export default function (providerContext: FtrProviderContext) {
skipIfNoDockerRegistry(providerContext);
let agentPolicy: any;
let packagePolicy: any;
before(async () => {
await getService('esArchiver').load('empty_kibana');
await getService('esArchiver').load('fleet/empty_fleet_server');
});
before(async function () {
let agentPolicyResponse = await supertest
.post(`/api/fleet/agent_policies`)
@ -79,6 +82,10 @@ export default function (providerContext: FtrProviderContext) {
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] });
});
after(async () => {
await getService('esArchiver').unload('empty_kibana');
await getService('esArchiver').unload('fleet/empty_fleet_server');
});
it('should fail on managed agent policies', async function () {
// update existing policy to managed

View file

@ -22,6 +22,10 @@ export default function (providerContext: FtrProviderContext) {
skipIfNoDockerRegistry(providerContext);
let agentPolicyId: string;
let packagePolicyId: string;
before(async () => {
await getService('esArchiver').load('empty_kibana');
await getService('esArchiver').load('fleet/empty_fleet_server');
});
before(async function () {
if (!server.enabled) {
@ -73,7 +77,10 @@ export default function (providerContext: FtrProviderContext) {
.send({ packagePolicyIds: [packagePolicyId] })
.expect(200);
});
after(async () => {
await getService('esArchiver').unload('fleet/empty_fleet_server');
await getService('esArchiver').unload('empty_kibana');
});
it('should succeed with a valid id', async function () {
await supertest.get(`/api/fleet/package_policies/${packagePolicyId}`).expect(200);
});

View file

@ -24,6 +24,10 @@ export default function (providerContext: FtrProviderContext) {
let managedAgentPolicyId: string;
let packagePolicyId: string;
let packagePolicyId2: string;
before(async () => {
await getService('esArchiver').load('empty_kibana');
await getService('esArchiver').load('fleet/empty_fleet_server');
});
before(async function () {
if (!server.enabled) {
@ -106,6 +110,11 @@ export default function (providerContext: FtrProviderContext) {
.send({ agentPolicyId });
});
after(async () => {
await getService('esArchiver').unload('fleet/empty_fleet_server');
await getService('esArchiver').unload('empty_kibana');
});
it('should fail on managed agent policies', async function () {
const { body } = await supertest
.put(`/api/fleet/package_policies/${packagePolicyId}`)

View file

@ -16,9 +16,13 @@ export default function (providerContext: FtrProviderContext) {
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
const esClient: Client = getService('legacyEs');
const esArchiver = getService('esArchiver');
describe('Settings - update', async function () {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('fleet/empty_fleet_server');
});
setupFleetAndAgents(providerContext);
const createdAgentPolicyIds: string[] = [];
@ -28,10 +32,12 @@ export default function (providerContext: FtrProviderContext) {
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId })
.expect(200)
);
await Promise.all(deletedPromises);
});
after(async () => {
await esArchiver.unload('fleet/empty_fleet_server');
});
it("should bump all agent policy's revision", async function () {
const { body: testPolicy1PostRes } = await supertest
.post(`/api/fleet/agent_policies`)

View file

@ -15,7 +15,7 @@ import { defineDockerServersConfig } from '@kbn/test';
// example: https://beats-ci.elastic.co/blue/organizations/jenkins/Ingest-manager%2Fpackage-storage/detail/snapshot/74/pipeline/257#step-302-log-1.
// It should be updated any time there is a new Docker image published for the Snapshot Distribution of the Package Registry.
export const dockerImage =
'docker.elastic.co/package-registry/distribution:99dadb957d76b704637150d34a7219345cc0aeef';
'docker.elastic.co/package-registry/distribution:c5925eb82898dfc3e879a521871c7383513804c7';
export default async function ({ readConfigFile }: FtrConfigProviderContext) {
const xPackAPITestsConfig = await readConfigFile(require.resolve('../api_integration/config.ts'));

View file

@ -1,21 +1,15 @@
{
"type": "doc",
"value": {
"id": "fleet-agents:agent1",
"index": ".kibana",
"id": "agent1",
"index": ".fleet-agents",
"source": {
"type": "fleet-agents",
"fleet-agents": {
"access_api_key_id": "api-key-1",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
},
"migrationVersion": {
"fleet-agents": "7.10.0"
}
"access_api_key_id": "api-key-1",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
}
}
}
@ -23,21 +17,15 @@
{
"type": "doc",
"value": {
"id": "fleet-agents:agent2",
"index": ".kibana",
"id": "agent2",
"index": ".fleet-agents",
"source": {
"type": "fleet-agents",
"fleet-agents": {
"access_api_key_id": "api-key-2",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
},
"migrationVersion": {
"fleet-agents": "7.10.0"
}
"access_api_key_id": "api-key-2",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
}
}
}
@ -45,21 +33,15 @@
{
"type": "doc",
"value": {
"id": "fleet-agents:agent3",
"index": ".kibana",
"id": "agent3",
"index": ".fleet-agents",
"source": {
"type": "fleet-agents",
"fleet-agents": {
"access_api_key_id": "api-key-3",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
},
"migrationVersion": {
"fleet-agents": "7.10.0"
}
"access_api_key_id": "api-key-3",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
}
}
}
@ -67,21 +49,15 @@
{
"type": "doc",
"value": {
"id": "fleet-agents:agent4",
"index": ".kibana",
"id": "agent4",
"index": ".fleet-agents",
"source": {
"type": "fleet-agents",
"fleet-agents": {
"access_api_key_id": "api-key-4",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
},
"migrationVersion": {
"fleet-agents": "7.10.0"
}
"access_api_key_id": "api-key-4",
"active": true,
"policy_id": "policy1",
"type": "PERMANENT",
"local_metadata": {},
"user_provided_metadata": {}
}
}
}
@ -89,79 +65,14 @@
{
"type": "doc",
"value": {
"id": "fleet-enrollment-api-keys:ed22ca17-e178-4cfe-8b02-54ea29fbd6d0",
"index": ".kibana",
"id": "ed22ca17-e178-4cfe-8b02-54ea29fbd6d0",
"index": ".fleet-enrollment-api-keys",
"source": {
"fleet-enrollment-api-keys" : {
"created_at" : "2019-10-10T16:31:12.518Z",
"name": "FleetEnrollmentKey:1",
"api_key_id" : "key",
"policy_id" : "policy1",
"active" : true
},
"type" : "fleet-enrollment-api-keys",
"references": [],
"migrationVersion": {
"fleet-enrollment-api-keys": "7.10.0"
}
}
}
}
{
"type": "doc",
"value": {
"id": "fleet-agent-events:event1",
"index": ".kibana",
"source": {
"type": "fleet-agent-events",
"fleet-agent-events": {
"agent_id": "agent1",
"type": "STATE",
"subtype": "STARTED",
"message": "State changed from STOPPED to STARTED",
"payload": "{\"previous_state\": \"STOPPED\"}",
"timestamp": "2019-09-20T17:30:22.950Z"
},
"migrationVersion": {
"fleet-agent-events": "7.10.0"
}
}
}
}
{
"type": "doc",
"value": {
"id": "fleet-agent-events:event2",
"index": ".kibana",
"source": {
"type": "fleet-agent-events",
"fleet-agent-events": {
"agent_id": "agent1",
"type": "STATE",
"subtype": "STOPPED",
"message": "State changed from RUNNING to STOPPED",
"payload": "{\"previous_state\": \"RUNNING\"}",
"timestamp": "2019-09-20T17:30:25.950Z"
}
}
}
}
{
"type": "doc",
"value": {
"id": "fleet-agent-actions:37ed51ff-e80f-4f2a-a62d-f4fa975e7d85",
"index": ".kibana",
"source": {
"type": "fleet-agent-actions",
"fleet-agent-actions": {
"agent_id": "agent1",
"created_at": "2019-09-04T15:04:07+0000",
"type": "RESUME",
"sent_at": "2019-09-04T15:03:07+0000"
}
"created_at" : "2019-10-10T16:31:12.518Z",
"name": "FleetEnrollmentKey:1",
"api_key_id" : "key",
"policy_id" : "policy1",
"active" : true
}
}
}

View file

@ -3081,3 +3081,444 @@
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-actions": {
}
},
"index": ".fleet-actions_1",
"mappings": {
"_meta": {
"migrationHash": "6527beea5a4a2f33acb599585ed4710442ece7f2"
},
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"action_id": {
"type": "keyword"
},
"agents": {
"type": "keyword"
},
"data": {
"enabled": false,
"type": "object"
},
"expiration": {
"type": "date"
},
"input_type": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-agents": {
}
},
"index": ".fleet-agents_1",
"mappings": {
"_meta": {
"migrationHash": "87cab95ac988d78a78d0d66bbf05361b65dcbacf"
},
"dynamic": "false",
"properties": {
"access_api_key_id": {
"type": "keyword"
},
"action_seq_no": {
"type": "integer"
},
"active": {
"type": "boolean"
},
"agent": {
"properties": {
"id": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"default_api_key": {
"type": "keyword"
},
"default_api_key_id": {
"type": "keyword"
},
"enrolled_at": {
"type": "date"
},
"last_checkin": {
"type": "date"
},
"last_checkin_status": {
"type": "keyword"
},
"last_updated": {
"type": "date"
},
"local_metadata": {
"properties": {
"elastic": {
"properties": {
"agent": {
"properties": {
"build": {
"properties": {
"original": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"id": {
"type": "keyword"
},
"log_level": {
"type": "keyword"
},
"snapshot": {
"type": "boolean"
},
"upgradeable": {
"type": "boolean"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 16,
"type": "keyword"
}
},
"type": "text"
}
}
}
}
},
"host": {
"properties": {
"architecture": {
"type": "keyword"
},
"hostname": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"id": {
"type": "keyword"
},
"ip": {
"fields": {
"keyword": {
"ignore_above": 64,
"type": "keyword"
}
},
"type": "text"
},
"mac": {
"fields": {
"keyword": {
"ignore_above": 17,
"type": "keyword"
}
},
"type": "text"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"os": {
"properties": {
"family": {
"type": "keyword"
},
"full": {
"fields": {
"keyword": {
"ignore_above": 128,
"type": "keyword"
}
},
"type": "text"
},
"kernel": {
"fields": {
"keyword": {
"ignore_above": 128,
"type": "keyword"
}
},
"type": "text"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"platform": {
"type": "keyword"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 32,
"type": "keyword"
}
},
"type": "text"
}
}
}
}
},
"packages": {
"type": "keyword"
},
"policy_coordinator_idx": {
"type": "integer"
},
"policy_id": {
"type": "keyword"
},
"policy_revision_idx": {
"type": "integer"
},
"shared_id": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"unenrolled_at": {
"type": "date"
},
"unenrollment_started_at": {
"type": "date"
},
"updated_at": {
"type": "date"
},
"upgrade_started_at": {
"type": "date"
},
"upgraded_at": {
"type": "date"
},
"user_provided_metadata": {
"enabled": false,
"type": "object"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-enrollment-api-keys": {
}
},
"index": ".fleet-enrollment-api-keys_1",
"mappings": {
"_meta": {
"migrationHash": "06bef724726f3bea9f474a09be0a7f7881c28d4a"
},
"dynamic": "false",
"properties": {
"active": {
"type": "boolean"
},
"api_key": {
"type": "keyword"
},
"api_key_id": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"expire_at": {
"type": "date"
},
"name": {
"type": "keyword"
},
"policy_id": {
"type": "keyword"
},
"updated_at": {
"type": "date"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-policies": {
}
},
"index": ".fleet-policies_1",
"mappings": {
"_meta": {
"migrationHash": "c2c2a49b19562942fa7c1ff1537e66e751cdb4fa"
},
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"coordinator_idx": {
"type": "integer"
},
"data": {
"enabled": false,
"type": "object"
},
"default_fleet_server": {
"type": "boolean"
},
"policy_id": {
"type": "keyword"
},
"revision_idx": {
"type": "integer"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-servers": {
}
},
"index": ".fleet-servers_1",
"mappings": {
"_meta": {
"migrationHash": "e2782448c7235ec9af66ca7997e867d715ac379c"
},
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"agent": {
"properties": {
"id": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"host": {
"properties": {
"architecture": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"ip": {
"type": "keyword"
},
"name": {
"type": "keyword"
}
}
},
"server": {
"properties": {
"id": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}

View file

@ -0,0 +1,440 @@
{
"type": "index",
"value": {
"aliases": {
".fleet-actions": {
}
},
"index": ".fleet-actions_1",
"mappings": {
"_meta": {
"migrationHash": "6527beea5a4a2f33acb599585ed4710442ece7f2"
},
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"action_id": {
"type": "keyword"
},
"agents": {
"type": "keyword"
},
"data": {
"enabled": false,
"type": "object"
},
"expiration": {
"type": "date"
},
"input_type": {
"type": "keyword"
},
"type": {
"type": "keyword"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-agents": {
}
},
"index": ".fleet-agents_1",
"mappings": {
"_meta": {
"migrationHash": "87cab95ac988d78a78d0d66bbf05361b65dcbacf"
},
"dynamic": "false",
"properties": {
"access_api_key_id": {
"type": "keyword"
},
"action_seq_no": {
"type": "integer"
},
"active": {
"type": "boolean"
},
"agent": {
"properties": {
"id": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"default_api_key": {
"type": "keyword"
},
"default_api_key_id": {
"type": "keyword"
},
"enrolled_at": {
"type": "date"
},
"last_checkin": {
"type": "date"
},
"last_checkin_status": {
"type": "keyword"
},
"last_updated": {
"type": "date"
},
"local_metadata": {
"properties": {
"elastic": {
"properties": {
"agent": {
"properties": {
"build": {
"properties": {
"original": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"id": {
"type": "keyword"
},
"log_level": {
"type": "keyword"
},
"snapshot": {
"type": "boolean"
},
"upgradeable": {
"type": "boolean"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 16,
"type": "keyword"
}
},
"type": "text"
}
}
}
}
},
"host": {
"properties": {
"architecture": {
"type": "keyword"
},
"hostname": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"id": {
"type": "keyword"
},
"ip": {
"fields": {
"keyword": {
"ignore_above": 64,
"type": "keyword"
}
},
"type": "text"
},
"mac": {
"fields": {
"keyword": {
"ignore_above": 17,
"type": "keyword"
}
},
"type": "text"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
}
}
},
"os": {
"properties": {
"family": {
"type": "keyword"
},
"full": {
"fields": {
"keyword": {
"ignore_above": 128,
"type": "keyword"
}
},
"type": "text"
},
"kernel": {
"fields": {
"keyword": {
"ignore_above": 128,
"type": "keyword"
}
},
"type": "text"
},
"name": {
"fields": {
"keyword": {
"ignore_above": 256,
"type": "keyword"
}
},
"type": "text"
},
"platform": {
"type": "keyword"
},
"version": {
"fields": {
"keyword": {
"ignore_above": 32,
"type": "keyword"
}
},
"type": "text"
}
}
}
}
},
"packages": {
"type": "keyword"
},
"policy_coordinator_idx": {
"type": "integer"
},
"policy_id": {
"type": "keyword"
},
"policy_revision_idx": {
"type": "integer"
},
"shared_id": {
"type": "keyword"
},
"type": {
"type": "keyword"
},
"unenrolled_at": {
"type": "date"
},
"unenrollment_started_at": {
"type": "date"
},
"updated_at": {
"type": "date"
},
"upgrade_started_at": {
"type": "date"
},
"upgraded_at": {
"type": "date"
},
"user_provided_metadata": {
"enabled": false,
"type": "object"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-enrollment-api-keys": {
}
},
"index": ".fleet-enrollment-api-keys_1",
"mappings": {
"_meta": {
"migrationHash": "06bef724726f3bea9f474a09be0a7f7881c28d4a"
},
"dynamic": "false",
"properties": {
"active": {
"type": "boolean"
},
"api_key": {
"type": "keyword"
},
"api_key_id": {
"type": "keyword"
},
"created_at": {
"type": "date"
},
"expire_at": {
"type": "date"
},
"name": {
"type": "keyword"
},
"policy_id": {
"type": "keyword"
},
"updated_at": {
"type": "date"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-policies": {
}
},
"index": ".fleet-policies_1",
"mappings": {
"_meta": {
"migrationHash": "c2c2a49b19562942fa7c1ff1537e66e751cdb4fa"
},
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"coordinator_idx": {
"type": "integer"
},
"data": {
"enabled": false,
"type": "object"
},
"default_fleet_server": {
"type": "boolean"
},
"policy_id": {
"type": "keyword"
},
"revision_idx": {
"type": "integer"
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}
{
"type": "index",
"value": {
"aliases": {
".fleet-servers": {
}
},
"index": ".fleet-servers_1",
"mappings": {
"_meta": {
"migrationHash": "e2782448c7235ec9af66ca7997e867d715ac379c"
},
"dynamic": "false",
"properties": {
"@timestamp": {
"type": "date"
},
"agent": {
"properties": {
"id": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
},
"host": {
"properties": {
"architecture": {
"type": "keyword"
},
"id": {
"type": "keyword"
},
"ip": {
"type": "keyword"
},
"name": {
"type": "keyword"
}
}
},
"server": {
"properties": {
"id": {
"type": "keyword"
},
"version": {
"type": "keyword"
}
}
}
}
},
"settings": {
"index": {
"number_of_replicas": "1",
"number_of_shards": "1"
}
}
}
}