mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
## Problem
While working on changes for bulk reassign https://github.com/elastic/kibana/issues/90437, I found that the server has a runtime error and returns a 500 if given an invalid or missing id.
<details><summary>server error stack trace</summary>
```
│ proc [kibana] server log [12:21:48.953] [error][fleet][plugins] TypeError: Cannot read property 'policy_revision_idx' of undefined
│ proc [kibana] at map (/Users/jfsiii/work/kibana/x-pack/plugins/fleet/server/services/agents/helpers.ts:15:34)
│ proc [kibana] at Array.map (<anonymous>)
│ proc [kibana] at getAgents (/Users/jfsiii/work/kibana/x-pack/plugins/fleet/server/services/agents/crud.ts:191:32)
│ proc [kibana] at runMicrotasks (<anonymous>)
│ proc [kibana] at processTicksAndRejections (internal/process/task_queues.js:93:5)
│ proc [kibana] at Object.reassignAgents (/Users/jfsiii/work/kibana/x-pack/plugins/fleet/server/services/agents/reassign.ts:91:9)
│ proc [kibana] at postBulkAgentsReassignHandler (/Users/jfsiii/work/kibana/x-pack/plugins/fleet/server/routes/agent/handlers.ts:314:21)
│ proc [kibana] at Router.handle (/Users/jfsiii/work/kibana/src/core/server/http/router/router.ts:272:30)
│ proc [kibana] at handler (/Users/jfsiii/work/kibana/src/core/server/http/router/router.ts:227:11)
│ proc [kibana] at exports.Manager.execute (/Users/jfsiii/work/kibana/node_modules/@hapi/hapi/lib/toolkit.js:60:28)
│ proc [kibana] at Object.internals.handler (/Users/jfsiii/work/kibana/node_modules/@hapi/hapi/lib/handler.js:46:20)
│ proc [kibana] at exports.execute (/Users/jfsiii/work/kibana/node_modules/@hapi/hapi/lib/handler.js:31:20)
│ proc [kibana] at Request._lifecycle (/Users/jfsiii/work/kibana/node_modules/@hapi/hapi/lib/request.js:370:32)
│ proc [kibana] at Request._execute (/Users/jfsiii/work/kibana/node_modules/@hapi/hapi/lib/request.js:279:9)
```
</details>
<details><summary>see test added in this PR fail on master</summary>
```
1) Fleet Endpoints
reassign agent(s)
bulk reassign agents
should allow to reassign multiple agents by id -- some invalid:
Error: expected 200 "OK", got 500 "Internal Server Error"
```
</details>
## Root cause
Debugging runtime error in `searchHitToAgent` found some TS type mismatches for the ES values being returned. Perhaps from one or more of the recent changes to ES client & Fleet Server. Based on `test:jest` and `test:ftr`, it appears the possible types are `GetResponse` or `SearchResponse`, instead of only an `ESSearchHit`.
https://github.com/elastic/kibana/pull/94632/files#diff-254d0f427979efc3b442f78762302eb28fb9c8857df68ea04f8d411e052f939cL11
While a `.search` result will include return matched values, a `.get` or `.mget` will return a row for each input and a `found: boolean`. e.g. `{ _id: "does-not-exist", found: false }`. The error occurs when [`searchHitToAgent`](1702cf98f0/x-pack/plugins/fleet/server/services/agents/helpers.ts (L11)
) is run on a get miss instead of a search hit.
## PR Changes
* Added a test to ensure it doesn't fail if invalid or missing IDs are given
* Moved the `bulk_reassign` tests to their own test section
* Filter out any missing results before calling `searchHitToAgent`, to match current behavior
* Consolidate repeated arguments into and code for getting agents into single [function](https://github.com/elastic/kibana/pull/94632/files#diff-f7377ed9ad56eaa8ea188b64e957e771ccc7a7652fd1eaf44251c25b930f8448R70-R87): and [TS type](https://github.com/elastic/kibana/pull/94632/files#diff-f7377ed9ad56eaa8ea188b64e957e771ccc7a7652fd1eaf44251c25b930f8448R61-R68)
* Rename some agent service functions to be more explicit (IMO) but behavior maintained. Same API names exported.
This moves toward the "one result (success or error) per given id" approach for https://github.com/elastic/kibana/issues/90437
### Checklist
- [x] [Unit or functional tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html) were updated or added to match the most common scenarios
# Conflicts:
# x-pack/plugins/fleet/server/services/agents/crud.ts
# x-pack/plugins/fleet/server/services/index.ts
125 lines
3.7 KiB
TypeScript
125 lines
3.7 KiB
TypeScript
/*
|
|
* 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 type { SavedObjectsClientContract, ElasticsearchClient } from 'kibana/server';
|
|
import Boom from '@hapi/boom';
|
|
|
|
import type { Agent } from '../../types';
|
|
import { agentPolicyService } from '../agent_policy';
|
|
import { AgentReassignmentError } from '../../errors';
|
|
|
|
import { getAgents, getAgentPolicyForAgent, updateAgent, bulkUpdateAgents } from './crud';
|
|
import type { GetAgentsOptions } from './index';
|
|
import { createAgentAction, bulkCreateAgentActions } from './actions';
|
|
|
|
export async function reassignAgent(
|
|
soClient: SavedObjectsClientContract,
|
|
esClient: ElasticsearchClient,
|
|
agentId: string,
|
|
newAgentPolicyId: string
|
|
) {
|
|
const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId);
|
|
if (!newAgentPolicy) {
|
|
throw Boom.notFound(`Agent policy not found: ${newAgentPolicyId}`);
|
|
}
|
|
|
|
await reassignAgentIsAllowed(soClient, esClient, agentId, newAgentPolicyId);
|
|
|
|
await updateAgent(esClient, agentId, {
|
|
policy_id: newAgentPolicyId,
|
|
policy_revision: null,
|
|
});
|
|
|
|
await createAgentAction(soClient, esClient, {
|
|
agent_id: agentId,
|
|
created_at: new Date().toISOString(),
|
|
type: 'INTERNAL_POLICY_REASSIGN',
|
|
});
|
|
}
|
|
|
|
export async function reassignAgentIsAllowed(
|
|
soClient: SavedObjectsClientContract,
|
|
esClient: ElasticsearchClient,
|
|
agentId: string,
|
|
newAgentPolicyId: string
|
|
) {
|
|
const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId);
|
|
if (agentPolicy?.is_managed) {
|
|
throw new AgentReassignmentError(
|
|
`Cannot reassign an agent from managed agent policy ${agentPolicy.id}`
|
|
);
|
|
}
|
|
|
|
const newAgentPolicy = await agentPolicyService.get(soClient, newAgentPolicyId);
|
|
if (newAgentPolicy?.is_managed) {
|
|
throw new AgentReassignmentError(
|
|
`Cannot reassign an agent to managed agent policy ${newAgentPolicy.id}`
|
|
);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
export async function reassignAgents(
|
|
soClient: SavedObjectsClientContract,
|
|
esClient: ElasticsearchClient,
|
|
options: { agents: Agent[] } | GetAgentsOptions,
|
|
newAgentPolicyId: string
|
|
): 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}`);
|
|
}
|
|
|
|
const allResults = 'agents' in options ? options.agents : await getAgents(esClient, options);
|
|
// which are allowed to unenroll
|
|
const settled = await Promise.allSettled(
|
|
allResults.map((agent) =>
|
|
reassignAgentIsAllowed(soClient, esClient, agent.id, newAgentPolicyId).then((_) => agent)
|
|
)
|
|
);
|
|
|
|
// Filter to agents that do not already use the new agent policy ID
|
|
const agentsToUpdate = allResults.filter((agent, index) => {
|
|
if (settled[index].status === 'fulfilled') {
|
|
if (agent.policy_id === newAgentPolicyId) {
|
|
settled[index] = {
|
|
status: 'rejected',
|
|
reason: new AgentReassignmentError(
|
|
`${agent.id} is already assigned to ${newAgentPolicyId}`
|
|
),
|
|
};
|
|
} else {
|
|
return true;
|
|
}
|
|
}
|
|
});
|
|
|
|
const res = await bulkUpdateAgents(
|
|
esClient,
|
|
agentsToUpdate.map((agent) => ({
|
|
agentId: agent.id,
|
|
data: {
|
|
policy_id: newAgentPolicyId,
|
|
policy_revision: null,
|
|
},
|
|
}))
|
|
);
|
|
|
|
const now = new Date().toISOString();
|
|
await bulkCreateAgentActions(
|
|
soClient,
|
|
esClient,
|
|
agentsToUpdate.map((agent) => ({
|
|
agent_id: agent.id,
|
|
created_at: now,
|
|
type: 'INTERNAL_POLICY_REASSIGN',
|
|
}))
|
|
);
|
|
|
|
return res;
|
|
}
|