[Fleet] Restrict integration changes for managed policies (#90675)

## Summary

- [x] Integrations cannot be added ~~, unless with a force flag~~
  - [x] API
  - [x] UI
  - [x] tests
- [x] Integrations cannot be removed ~~, unless with a force flag~~
  - [x] API
  - [x] UI
  - [x] tests

closes https://github.com/elastic/kibana/issues/90445
refs https://github.com/elastic/kibana/issues/89617

### Cannot add integrations to managed policy

<img height="400" alt="Screen Shot 2021-02-08 at 1 56 32 PM" src="https://user-images.githubusercontent.com/57655/107277261-25c48300-6a22-11eb-936a-0a7361667093.png">

### Cannot delete integrations from managed policy

<img  alt="Screen Shot 2021-02-08 at 3 05 16 PM" src="https://user-images.githubusercontent.com/57655/107277318-337a0880-6a22-11eb-836f-fc66b510d257.png">

### 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
This commit is contained in:
John Schulz 2021-02-10 17:04:01 -05:00 committed by GitHub
parent 4cd0548f48
commit c92af5a4d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 257 additions and 16 deletions

View file

@ -79,11 +79,10 @@ export const createPackagePolicyHandler: RequestHandler<
const esClient = context.core.elasticsearch.client.asCurrentUser;
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
let newData = { ...request.body };
try {
newData = await packagePolicyService.runExternalCallbacks(
const newData = await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
newData,
{ ...request.body },
context,
request
);

View file

@ -36,7 +36,11 @@ import {
FleetServerPolicy,
AGENT_POLICY_INDEX,
} from '../../common';
import { AgentPolicyNameExistsError, AgentPolicyDeletionError } from '../errors';
import {
AgentPolicyNameExistsError,
AgentPolicyDeletionError,
IngestManagerError,
} from '../errors';
import { createAgentPolicyAction, listAgents } from './agents';
import { packagePolicyService } from './package_policy';
import { outputService } from './output';
@ -382,6 +386,10 @@ class AgentPolicyService {
throw new Error('Agent policy not found');
}
if (oldAgentPolicy.is_managed) {
throw new IngestManagerError(`Cannot update integrations of managed policy ${id}`);
}
return await this._update(
soClient,
esClient,
@ -409,6 +417,10 @@ class AgentPolicyService {
throw new Error('Agent policy not found');
}
if (oldAgentPolicy.is_managed) {
throw new IngestManagerError(`Cannot remove integrations of managed policy ${id}`);
}
return await this._update(
soClient,
esClient,

View file

@ -25,6 +25,7 @@ import {
doesAgentPolicyAlreadyIncludePackage,
} from '../../common';
import { PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../constants';
import { IngestManagerError, ingestErrorToResponseOptions } from '../errors';
import {
NewPackagePolicy,
UpdatePackagePolicy,
@ -63,15 +64,20 @@ class PackagePolicyService {
const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id);
if (!parentAgentPolicy) {
throw new Error('Agent policy not found');
} else {
if (
(parentAgentPolicy.package_policies as PackagePolicy[]).find(
(siblingPackagePolicy) => siblingPackagePolicy.name === packagePolicy.name
)
) {
throw new Error('There is already a package with the same name on this agent policy');
}
}
if (parentAgentPolicy.is_managed) {
throw new IngestManagerError(
`Cannot add integrations to managed policy ${parentAgentPolicy.id}`
);
}
if (
(parentAgentPolicy.package_policies as PackagePolicy[]).find(
(siblingPackagePolicy) => siblingPackagePolicy.name === packagePolicy.name
)
) {
throw new Error('There is already a package with the same name on this agent policy');
}
// Add ids to stream
const packagePolicyId = options?.id || uuid.v4();
let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) =>
@ -285,6 +291,9 @@ class PackagePolicyService {
if (!parentAgentPolicy) {
throw new Error('Agent policy not found');
} else {
if (parentAgentPolicy.is_managed) {
throw new IngestManagerError(`Cannot update integrations of managed policy ${id}`);
}
if (
(parentAgentPolicy.package_policies as PackagePolicy[]).find(
(siblingPackagePolicy) =>
@ -295,7 +304,7 @@ class PackagePolicyService {
}
}
let inputs = await restOfPackagePolicy.inputs.map((input) =>
let inputs = restOfPackagePolicy.inputs.map((input) =>
assignStreamIdToInput(oldPackagePolicy.id, input)
);
@ -363,10 +372,11 @@ class PackagePolicyService {
name: packagePolicy.name,
success: true,
});
} catch (e) {
} catch (error) {
result.push({
id,
success: false,
...ingestErrorToResponseOptions(error),
});
}
}

View file

@ -37,6 +37,7 @@ export default function ({ loadTestFile }) {
loadTestFile(require.resolve('./package_policy/create'));
loadTestFile(require.resolve('./package_policy/update'));
loadTestFile(require.resolve('./package_policy/get'));
loadTestFile(require.resolve('./package_policy/delete'));
// Agent policies
loadTestFile(require.resolve('./agent_policy/index'));

View file

@ -4,7 +4,7 @@
* 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';
import { warnAndSkipTest } from '../../helpers';
@ -39,6 +39,52 @@ export default function ({ getService }: FtrProviderContext) {
.send({ agentPolicyId });
});
it('should fail for managed agent policies', async function () {
if (server.enabled) {
// get a managed policy
const {
body: { item: managedPolicy },
} = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: `Managed policy from ${Date.now()}`,
namespace: 'default',
is_managed: true,
});
// try to add an integration to the managed policy
const { body } = await supertest
.post(`/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'filetest-1',
description: '',
namespace: 'default',
policy_id: managedPolicy.id,
enabled: true,
output_id: '',
inputs: [],
package: {
name: 'filetest',
title: 'For File Tests',
version: '0.1.0',
},
})
.expect(400);
expect(body.statusCode).to.be(400);
expect(body.message).to.contain('Cannot add integrations to managed policy');
// delete policy we just made
await supertest.post(`/api/fleet/agent_policies/delete`).set('kbn-xsrf', 'xxxx').send({
agentPolicyId: managedPolicy.id,
});
} else {
warnAndSkipTest(this, log);
}
});
it('should work with valid values', async function () {
if (server.enabled) {
await supertest

View file

@ -0,0 +1,127 @@
/*
* 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';
import { skipIfNoDockerRegistry } from '../../helpers';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
// use function () {} and not () => {} here
// because `this` has to point to the Mocha context
// see https://mochajs.org/#arrow-functions
describe('Package Policy - delete', async function () {
skipIfNoDockerRegistry(providerContext);
let agentPolicy: any;
let packagePolicy: any;
before(async function () {
let agentPolicyResponse = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'Test policy',
namespace: 'default',
is_managed: false,
});
// if one already exists, re-use that
if (agentPolicyResponse.body.statusCode === 409) {
const errorRegex = /^agent policy \'(?<id>[\w,\-]+)\' already exists/i;
const result = errorRegex.exec(agentPolicyResponse.body.message);
if (result?.groups?.id) {
agentPolicyResponse = await supertest
.put(`/api/fleet/agent_policies/${result.groups.id}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'Test policy',
namespace: 'default',
is_managed: false,
});
}
}
agentPolicy = agentPolicyResponse.body.item;
const { body: packagePolicyResponse } = await supertest
.post(`/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'filetest-1',
description: '',
namespace: 'default',
policy_id: agentPolicy.id,
enabled: true,
output_id: '',
inputs: [],
package: {
name: 'filetest',
title: 'For File Tests',
version: '0.1.0',
},
});
packagePolicy = packagePolicyResponse.item;
});
after(async function () {
await supertest
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId: agentPolicy.id });
await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] });
});
it('should fail on managed agent policies', async function () {
// update existing policy to managed
await supertest
.put(`/api/fleet/agent_policies/${agentPolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: agentPolicy.name,
namespace: agentPolicy.namespace,
is_managed: true,
})
.expect(200);
// try to delete
const { body: results } = await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] })
.expect(200);
// delete always succeeds (returns 200) with Array<{success: boolean}>
expect(Array.isArray(results));
expect(results.length).to.be(1);
expect(results[0].success).to.be(false);
expect(results[0].body.message).to.contain('Cannot remove integrations of managed policy');
// revert existing policy to unmanaged
await supertest
.put(`/api/fleet/agent_policies/${agentPolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: agentPolicy.name,
namespace: agentPolicy.namespace,
is_managed: false,
})
.expect(200);
});
it('should work for unmanaged policies', async function () {
await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] });
});
});
}

View file

@ -4,7 +4,7 @@
* 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';
import { skipIfNoDockerRegistry } from '../../helpers';
@ -21,6 +21,7 @@ export default function (providerContext: FtrProviderContext) {
describe('Package Policy - update', async function () {
skipIfNoDockerRegistry(providerContext);
let agentPolicyId: string;
let managedAgentPolicyId: string;
let packagePolicyId: string;
let packagePolicyId2: string;
@ -35,8 +36,30 @@ export default function (providerContext: FtrProviderContext) {
name: 'Test policy',
namespace: 'default',
});
agentPolicyId = agentPolicyResponse.item.id;
const { body: managedAgentPolicyResponse } = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'Test managed policy',
namespace: 'default',
is_managed: true,
});
// if one already exists, re-use that
const managedExists = managedAgentPolicyResponse.statusCode === 409;
if (managedExists) {
const errorRegex = /^agent policy \'(?<id>[\w,\-]+)\' already exists/i;
const result = errorRegex.exec(managedAgentPolicyResponse.message);
if (result?.groups?.id) {
managedAgentPolicyId = result.groups.id;
}
} else {
managedAgentPolicyId = managedAgentPolicyResponse.item.id;
}
const { body: packagePolicyResponse } = await supertest
.post(`/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
@ -83,6 +106,29 @@ export default function (providerContext: FtrProviderContext) {
.send({ agentPolicyId });
});
it('should fail on managed agent policies', async function () {
const { body } = await supertest
.put(`/api/fleet/package_policies/${packagePolicyId}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'filetest-1',
description: '',
namespace: 'updated_namespace',
policy_id: managedAgentPolicyId,
enabled: true,
output_id: '',
inputs: [],
package: {
name: 'filetest',
title: 'For File Tests',
version: '0.1.0',
},
})
.expect(400);
expect(body.message).to.contain('Cannot update integrations of managed policy');
});
it('should work with valid values', async function () {
await supertest
.put(`/api/fleet/package_policies/${packagePolicyId}`)