[Fleet] Don't upgrade agent(s) in a managed policy (#91303)

## Summary

 - Make sure any agents requesting to be upgraded, are not enrolled in a managed policy.
 - `force: true` will only bypass agent / kibana version checks. It will not bypass managed policy check. To workaround, the enrolled policy should be changed to unmanaged (`is_managed: false`) as we do with enroll, reassign, etc.
 - Took more efficient approach to bulk actions. One `bulkGet` for N agents/policies vs N `get`s  approach used for bulk reassignment of agents. See discussion in https://github.com/elastic/kibana/pull/88688/files#r568941761
 - [x] API
 - [ ] UI
 - [x] tests

### 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


### Manual tests
#### upgrade one
```
curl --location --request POST 'http://localhost:5601/api/fleet/agents/8d9748e0-6d52-11eb-8cbd-47e38cd1c8de/upgrade' --header 'kbn-xsrf: <string>' --header 'Content-Type: application/json' --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' --data-raw '{
    "version": "8.0.0"
}'
{"statusCode":400,"error":"Bad Request","message":"Cannot upgrade agent 8d9748e0-6d52-11eb-8cbd-47e38cd1c8de in managed policy bf319100-6d50-11eb-8859-15a87f509a99"}
```

```
curl --location --request POST 'http://localhost:5601/api/fleet/agents/8d9748e0-6d52-11eb-8cbd-47e38cd1c8de/upgrade' --header 'kbn-xsrf: <string>' --header 'Content-Type: application/json' --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' --data-raw '{
    "version": "8.0.0", "force": true
}'
{"statusCode":400,"error":"Bad Request","message":"Cannot upgrade agent 8d9748e0-6d52-11eb-8cbd-47e38cd1c8de in managed policy bf319100-6d50-11eb-8859-15a87f509a99"}
```

#### bulk upgrade
```
curl --location --request POST 'http://localhost:5601/api/fleet/agents/bulk_upgrade' --header 'kbn-xsrf: <string>' --header 'Content-Type: application/json' --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' --data-raw '{
    "version": "8.0.0",
    "agents": [
        "8d9748e0-6d52-11eb-8cbd-47e38cd1c8de"
    ]
}'
{}
```

```
curl --location --request POST 'http://localhost:5601/api/fleet/agents/bulk_upgrade' --header 'kbn-xsrf: <string>' --header 'Content-Type: application/json' --header 'Authorization: Basic ZWxhc3RpYzpjaGFuZ2VtZQ==' --data-raw '{
    "version": "8.0.0",
    "agents": [
        "8d9748e0-6d52-11eb-8cbd-47e38cd1c8de"
    ], "force": true
}'
{"statusCode":400,"error":"Bad Request","message":"Cannot update agent in managed policy bf319100-6d50-11eb-8859-15a87f509a99"}```
```
This commit is contained in:
John Schulz 2021-02-15 13:36:39 -05:00 committed by GitHub
parent f1f206b2c8
commit 0a5e054fdc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 136 additions and 5 deletions

View file

@ -209,6 +209,21 @@ class AgentPolicyService {
return agentPolicy;
}
public async getByIDs(
soClient: SavedObjectsClientContract,
ids: string[],
options: { fields?: string[] } = {}
): Promise<AgentPolicy[]> {
const objects = ids.map((id) => ({ ...options, id, type: SAVED_OBJECT_TYPE }));
const agentPolicySO = await soClient.bulkGet<AgentPolicySOAttributes>(objects);
return agentPolicySO.saved_objects.map((so) => ({
id: so.id,
version: so.version,
...so.attributes,
}));
}
public async list(
soClient: SavedObjectsClientContract,
options: ListWithKuery & {

View file

@ -8,8 +8,16 @@
import { ElasticsearchClient, SavedObjectsClientContract } from 'src/core/server';
import { AgentAction, AgentActionSOAttributes } from '../../types';
import { AGENT_ACTION_SAVED_OBJECT_TYPE } from '../../constants';
import { agentPolicyService } from '../../services';
import { IngestManagerError } from '../../errors';
import { bulkCreateAgentActions, createAgentAction } from './actions';
import { getAgents, listAllAgents, updateAgent, bulkUpdateAgents } from './crud';
import {
getAgents,
listAllAgents,
updateAgent,
bulkUpdateAgents,
getAgentPolicyForAgent,
} from './crud';
import { isAgentUpgradeable } from '../../../common/services';
import { appContextService } from '../app_context';
@ -31,6 +39,14 @@ export async function sendUpgradeAgentAction({
version,
source_uri: sourceUri,
};
const agentPolicy = await getAgentPolicyForAgent(soClient, esClient, agentId);
if (agentPolicy?.is_managed) {
throw new IngestManagerError(
`Cannot upgrade agent ${agentId} in managed policy ${agentPolicy.id}`
);
}
await createAgentAction(soClient, esClient, {
agent_id: agentId,
created_at: now,
@ -89,19 +105,40 @@ export async function sendUpgradeAgentsActions(
showInactive: false,
})
).agents;
const agentsToUpdate = options.force
// upgradeable if they pass the version check
const upgradeableAgents = options.force
? agents
: agents.filter((agent) => isAgentUpgradeable(agent, kibanaVersion));
// get any policy ids from upgradable agents
const policyIdsToGet = new Set(
upgradeableAgents.filter((agent) => agent.policy_id).map((agent) => agent.policy_id!)
);
// get the agent policies for those ids
const agentPolicies = await agentPolicyService.getByIDs(soClient, Array.from(policyIdsToGet), {
fields: ['is_managed'],
});
// throw if any of those agent policies are managed
for (const policy of agentPolicies) {
if (policy.is_managed) {
throw new IngestManagerError(`Cannot upgrade agent in managed policy ${policy.id}`);
}
}
// Create upgrade action for each agent
const now = new Date().toISOString();
const data = {
version: options.version,
source_uri: options.sourceUri,
};
// Create upgrade action for each agent
await bulkCreateAgentActions(
soClient,
esClient,
agentsToUpdate.map((agent) => ({
upgradeableAgents.map((agent) => ({
agent_id: agent.id,
created_at: now,
data,
@ -113,7 +150,7 @@ export async function sendUpgradeAgentsActions(
return await bulkUpdateAgents(
soClient,
esClient,
agentsToUpdate.map((agent) => ({
upgradeableAgents.map((agent) => ({
agentId: agent.id,
data: {
upgraded_at: null,

View file

@ -427,5 +427,84 @@ export default function (providerContext: FtrProviderContext) {
})
.expect(400);
});
describe('fleet upgrade agent(s) in a managed policy', function () {
it('should respond 400 to bulk upgrade and not update the agent SOs', async () => {
// update enrolled policy to managed
await supertest.put(`/api/fleet/agent_policies/policy1`).set('kbn-xsrf', 'xxxx').send({
name: 'Test policy',
namespace: 'default',
is_managed: true,
});
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.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') },
},
},
},
});
// attempt to upgrade agent in managed policy
const { body } = await supertest
.post(`/api/fleet/agents/bulk_upgrade`)
.set('kbn-xsrf', 'xxx')
.send({
version: kibanaVersion,
agents: ['agent1', 'agent2'],
})
.expect(400);
expect(body.message).to.contain('Cannot upgrade agent in managed policy policy1');
const [agent1data, agent2data] = await Promise.all([
supertest.get(`/api/fleet/agents/agent1`),
supertest.get(`/api/fleet/agents/agent2`),
]);
expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined');
expect(typeof agent2data.body.item.upgrade_started_at).to.be('undefined');
});
it('should respond 400 to upgrade and not update the agent SOs', async () => {
// update enrolled policy to managed
await supertest.put(`/api/fleet/agent_policies/policy1`).set('kbn-xsrf', 'xxxx').send({
name: 'Test policy',
namespace: 'default',
is_managed: true,
});
const kibanaVersion = await kibanaServer.version.get();
await kibanaServer.savedObjects.update({
id: 'agent1',
type: AGENT_SAVED_OBJECT_TYPE,
attributes: {
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`)
.set('kbn-xsrf', 'xxx')
.send({ version: kibanaVersion })
.expect(400);
expect(body.message).to.contain('Cannot upgrade agent agent1 in managed policy policy1');
const agent1data = await supertest.get(`/api/fleet/agents/agent1`);
expect(typeof agent1data.body.item.upgrade_started_at).to.be('undefined');
});
});
});
}