[Fleet] Delete one package policy API (#138560)

This commit is contained in:
Nicolas Chaulet 2022-08-11 08:38:51 -04:00 committed by GitHub
parent bc1fe28b23
commit b0cd6f7079
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 456 additions and 162 deletions

View file

@ -2961,6 +2961,48 @@
"$ref": "#/components/parameters/kbn_xsrf"
}
]
},
"delete": {
"summary": "Package policy - Delete",
"tags": [],
"operationId": "delete-package-policy",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
]
}
}
}
}
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "packagePolicyId",
"in": "path",
"required": true
},
{
"schema": {
"type": "boolean"
},
"name": "force",
"in": "query"
}
]
}
},
"/outputs": {
@ -4306,6 +4348,10 @@
"description": "list of agent IDs"
}
]
},
"force": {
"type": "boolean",
"description": "Force upgrade, skipping validation (should be used with caution)"
}
},
"required": [
@ -4315,32 +4361,21 @@
},
"upgrade_agent": {
"title": "Upgrade agent",
"oneOf": [
{
"type": "object",
"properties": {
"version": {
"type": "string"
}
},
"required": [
"version"
]
"type": "object",
"properties": {
"version": {
"type": "string"
},
{
"type": "object",
"properties": {
"version": {
"type": "string"
},
"source_uri": {
"type": "string"
}
},
"required": [
"version"
]
"source_uri": {
"type": "string"
},
"force": {
"type": "boolean",
"description": "Force upgrade, skipping validation (should be used with caution)"
}
},
"required": [
"version"
]
},
"agent_action": {

View file

@ -1820,6 +1820,32 @@ paths:
- sucess
parameters:
- $ref: '#/components/parameters/kbn_xsrf'
delete:
summary: Package policy - Delete
tags: []
operationId: delete-package-policy
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
id:
type: string
required:
- id
parameters:
- schema:
type: string
name: packagePolicyId
in: path
required: true
- schema:
type: boolean
name: force
in: query
/outputs:
get:
summary: Outputs
@ -2716,26 +2742,25 @@ components:
items:
type: string
description: list of agent IDs
force:
type: boolean
description: Force upgrade, skipping validation (should be used with caution)
required:
- agents
- version
upgrade_agent:
title: Upgrade agent
oneOf:
- type: object
properties:
version:
type: string
required:
- version
- type: object
properties:
version:
type: string
source_uri:
type: string
required:
- version
type: object
properties:
version:
type: string
source_uri:
type: string
force:
type: boolean
description: Force upgrade, skipping validation (should be used with caution)
required:
- version
agent_action:
title: Agent action
oneOf:

View file

@ -45,3 +45,29 @@ put:
- sucess
parameters:
- $ref: ../components/headers/kbn_xsrf.yaml
delete:
summary: Package policy - Delete
tags: []
operationId: delete-package-policy
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
id:
type: string
required:
- id
parameters:
- schema:
type: string
name: packagePolicyId
in: path
required: true
- schema:
type: boolean
name: force
in: query

View file

@ -59,6 +59,11 @@ export type DeletePackagePoliciesResponse = Array<{
success: boolean;
package?: PackagePolicyPackage;
policy_id?: string;
// Support generic errors
statusCode?: number;
body?: {
message: string;
};
}>;
export interface UpgradePackagePolicyBaseResponse {

View file

@ -24,6 +24,7 @@ import type {
DryRunPackagePoliciesRequestSchema,
FleetRequestHandler,
PackagePolicy,
DeleteOnePackagePolicyRequestSchema,
} from '../../types';
import type {
CreatePackagePolicyResponse,
@ -290,6 +291,53 @@ export const deletePackagePolicyHandler: RequestHandler<
}
};
export const deleteOnePackagePolicyHandler: RequestHandler<
TypeOf<typeof DeleteOnePackagePolicyRequestSchema.params>,
TypeOf<typeof DeleteOnePackagePolicyRequestSchema.query>,
unknown
> = async (context, request, response) => {
const coreContext = await context.core;
const soClient = coreContext.savedObjects.client;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
try {
const res = await packagePolicyService.delete(
soClient,
esClient,
[request.params.packagePolicyId],
{ user, force: request.query.force, skipUnassignFromAgentPolicies: request.query.force }
);
if (
res[0] &&
res[0].success === false &&
res[0].statusCode !== 404 // ignore 404 to allow that call to be idempotent
) {
return response.customError({
statusCode: res[0].statusCode ?? 500,
body: res[0].body,
});
}
try {
await packagePolicyService.runExternalCallbacks(
'postPackagePolicyDelete',
res,
context,
request
);
} catch (error) {
const logger = appContextService.getLogger();
logger.error(`An error occurred executing external callback: ${error}`);
logger.error(error);
}
return response.ok({
body: { id: request.params.packagePolicyId },
});
} catch (error) {
return defaultIngestErrorHandler({ error, response });
}
};
export const upgradePackagePolicyHandler: RequestHandler<
unknown,
unknown,

View file

@ -14,6 +14,7 @@ import {
DeletePackagePoliciesRequestSchema,
UpgradePackagePoliciesRequestSchema,
DryRunPackagePoliciesRequestSchema,
DeleteOnePackagePolicyRequestSchema,
} from '../../types';
import type { FleetAuthzRouter } from '../security';
@ -26,6 +27,7 @@ import {
upgradePackagePolicyHandler,
dryRunUpgradePackagePolicyHandler,
getOrphanedPackagePolicies,
deleteOnePackagePolicyHandler,
} from './handlers';
export const registerRoutes = (router: FleetAuthzRouter) => {
@ -100,6 +102,17 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
deletePackagePolicyHandler
);
router.delete(
{
path: PACKAGE_POLICY_API_ROUTES.INFO_PATTERN,
validate: DeleteOnePackagePolicyRequestSchema,
fleetAuthz: {
integrations: { writeIntegrationPolicies: true },
},
},
deleteOnePackagePolicyHandler
);
// Upgrade
router.post(
{

View file

@ -40,6 +40,15 @@ export const DeletePackagePoliciesRequestSchema = {
}),
};
export const DeleteOnePackagePolicyRequestSchema = {
params: schema.object({
packagePolicyId: schema.string(),
}),
query: schema.object({
force: schema.maybe(schema.boolean()),
}),
};
export const UpgradePackagePoliciesRequestSchema = {
body: schema.object({
packagePolicyIds: schema.arrayOf(schema.string()),

View file

@ -12,143 +12,276 @@ 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 () {
describe('Package Policy - delete', () => {
skipIfNoDockerRegistry(providerContext);
let agentPolicy: any;
let packagePolicy: any;
before(async () => {
await getService('esArchiver').load('x-pack/test/functional/es_archives/empty_kibana');
await getService('esArchiver').load(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
});
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,
});
describe('Delete one', () => {
let agentPolicy: any;
let packagePolicy: any;
before(async () => {
await getService('esArchiver').load('x-pack/test/functional/es_archives/empty_kibana');
await getService('esArchiver').load(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
});
beforeEach(async () => {
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,
force: true,
});
// 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,
force: true,
});
}
}
}
agentPolicy = agentPolicyResponse.body.item;
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;
const { body: packagePolicyResponse } = await supertest
.post(`/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
id: 'filetest-1',
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;
});
afterEach(async () => {
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({ force: true, packagePolicyIds: [packagePolicy.id] });
});
after(async () => {
await getService('esArchiver').unload('x-pack/test/functional/es_archives/empty_kibana');
await getService('esArchiver').unload(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
});
it('should fail on hosted agent policies', async () => {
// update existing policy to hosted
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: result } = await supertest
.delete(`/api/fleet/package_policies/${packagePolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.expect(400);
expect(result.message).to.contain('Cannot remove integrations of hosted agent policy');
// same, but with force
await supertest
.delete(`/api/fleet/package_policies/${packagePolicy.id}?force=true`)
.set('kbn-xsrf', 'xxxx')
.expect(200);
// revert existing policy to regular
await supertest
.put(`/api/fleet/agent_policies/${agentPolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: agentPolicy.name,
namespace: agentPolicy.namespace,
is_managed: false,
force: true,
})
.expect(200);
});
it('should work for regular policies', async function () {
await supertest
.delete(`/api/fleet/package_policies/${packagePolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.expect(200);
});
it('should work if the package policy is already deleted', async function () {
await supertest
.delete(`/api/fleet/package_policies/${packagePolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.expect(200);
await supertest
.delete(`/api/fleet/package_policies/${packagePolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.expect(200);
});
});
describe('Delete bulk', () => {
let agentPolicy: any;
let packagePolicy: any;
before(async () => {
await getService('esArchiver').load('x-pack/test/functional/es_archives/empty_kibana');
await getService('esArchiver').load(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
});
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,
});
after(async function () {
await supertest
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId: agentPolicy.id });
// 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,
force: true,
});
}
}
agentPolicy = agentPolicyResponse.body.item;
await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ force: true, packagePolicyIds: [packagePolicy.id] });
});
after(async () => {
await getService('esArchiver').unload('x-pack/test/functional/es_archives/empty_kibana');
await getService('esArchiver').unload(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
});
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;
});
it('should fail on hosted agent policies', async function () {
// update existing policy to hosted
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);
after(async function () {
await supertest
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId: agentPolicy.id });
// try to delete
const { body: results } = await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] })
.expect(200);
await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ force: true, packagePolicyIds: [packagePolicy.id] });
});
after(async () => {
await getService('esArchiver').unload('x-pack/test/functional/es_archives/empty_kibana');
await getService('esArchiver').unload(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
});
// 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 hosted agent policy'
);
it('should fail on hosted agent policies', async function () {
// update existing policy to hosted
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);
// same, but with force
const { body: resultsWithForce } = await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ force: true, packagePolicyIds: [packagePolicy.id] })
.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(resultsWithForce));
expect(resultsWithForce.length).to.be(1);
expect(resultsWithForce[0].success).to.be(true);
// 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 hosted agent policy'
);
// revert existing policy to regular
await supertest
.put(`/api/fleet/agent_policies/${agentPolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: agentPolicy.name,
namespace: agentPolicy.namespace,
is_managed: false,
force: true,
})
.expect(200);
});
// same, but with force
const { body: resultsWithForce } = await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ force: true, packagePolicyIds: [packagePolicy.id] })
.expect(200);
it('should work for regular policies', async function () {
await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] });
// delete always succeeds (returns 200) with Array<{success: boolean}>
expect(Array.isArray(resultsWithForce));
expect(resultsWithForce.length).to.be(1);
expect(resultsWithForce[0].success).to.be(true);
// revert existing policy to regular
await supertest
.put(`/api/fleet/agent_policies/${agentPolicy.id}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: agentPolicy.name,
namespace: agentPolicy.namespace,
is_managed: false,
force: true,
})
.expect(200);
});
it('should work for regular policies', async function () {
await supertest
.post(`/api/fleet/package_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ packagePolicyIds: [packagePolicy.id] })
.expect(200);
});
});
});
}