[Fleet] Move callbacks from http methods to package policy service (#149272)

Closes https://github.com/elastic/kibana/issues/129383

This PR ensures that fleet callbacks are called regardless if operations
on a package policy are performed via the api or directly using the
package policy service.
This commit is contained in:
Søren Louv-Jansen 2023-02-01 15:32:59 +01:00 committed by GitHub
parent eabf08bbab
commit a1251a93c2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 470 additions and 453 deletions

View file

@ -225,6 +225,9 @@ export class APMPlugin
coreStartPromise: getCoreStart(),
plugins: resourcePlugins,
config: currentConfig,
}).catch((e) => {
this.logger?.error('Failed to register APM Fleet policy callbacks');
this.logger?.error(e);
});
// This will add an API key to all existing APM package policies
@ -232,6 +235,9 @@ export class APMPlugin
coreStartPromise: getCoreStart(),
pluginStartPromise: getPluginStart(),
logger: this.logger,
}).catch((e) => {
this.logger?.error('Failed to add API keys to APM package policies');
this.logger?.error(e);
});
const taskManager = plugins.taskManager;

View file

@ -75,7 +75,6 @@ function onPackagePolicyDelete({
logger: Logger;
}): PostPackagePolicyDeleteCallback {
return async (packagePolicies) => {
// console.log(`packagePolicyDelete:`, packagePolicies);
const promises = packagePolicies.map(async (packagePolicy) => {
if (packagePolicy.package?.name !== 'apm') {
return packagePolicy;

View file

@ -5,7 +5,12 @@
* 2.0.
*/
import { coreMock, httpServerMock } from '@kbn/core/server/mocks';
import {
coreMock,
elasticsearchServiceMock,
httpServerMock,
savedObjectsClientMock,
} from '@kbn/core/server/mocks';
import {
createPackagePolicyServiceMock,
createArtifactsClientMock,
@ -43,6 +48,7 @@ import {
} from '@kbn/core/server';
import { securityMock } from '@kbn/security-plugin/server/mocks';
import { licensingMock } from '@kbn/licensing-plugin/server/mocks';
import * as onPackagePolicyPostCreateCallback from './fleet_integration/fleet_integration';
const chance = new Chance();
@ -147,12 +153,18 @@ describe('Cloud Security Posture Plugin', () => {
});
it('should initialize when new package is created', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
fleetMock.packageService.asInternalUser.getInstallation.mockImplementationOnce(
async (): Promise<Installation | undefined> => {
return;
}
);
const onPackagePolicyPostCreateCallbackSpy = jest
.spyOn(onPackagePolicyPostCreateCallback, 'onPackagePolicyPostCreateCallback')
.mockResolvedValue();
const packageMock = createPackagePolicyMock();
packageMock.package!.name = CLOUD_SECURITY_POSTURE_PACKAGE_NAME;
@ -172,19 +184,30 @@ describe('Cloud Security Posture Plugin', () => {
await mockPlugins.fleet.fleetSetupCompleted();
// Assert
expect(onPackagePolicyPostCreateCallbackSpy).not.toHaveBeenCalled();
expect(fleetMock.packageService.asInternalUser.getInstallation).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(0);
expect(packagePolicyPostCreateCallbacks.length).toBeGreaterThan(0);
for (const cb of packagePolicyPostCreateCallbacks) {
await cb(packageMock, contextMock, httpServerMock.createKibanaRequest());
await cb(
packageMock,
soClient,
esClient,
contextMock,
httpServerMock.createKibanaRequest()
);
}
expect(onPackagePolicyPostCreateCallbackSpy).toHaveBeenCalled();
expect(spy).toHaveBeenCalledTimes(1);
});
it('should not initialize when other package is created', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
fleetMock.packageService.asInternalUser.getInstallation.mockImplementationOnce(
async (): Promise<Installation | undefined> => {
return;
@ -216,7 +239,13 @@ describe('Cloud Security Posture Plugin', () => {
expect(packagePolicyPostCreateCallbacks.length).toBeGreaterThan(0);
for (const cb of packagePolicyPostCreateCallbacks) {
await cb(packageMock, contextMock, httpServerMock.createKibanaRequest());
await cb(
packageMock,
soClient,
esClient,
contextMock,
httpServerMock.createKibanaRequest()
);
}
expect(spy).toHaveBeenCalledTimes(0);
@ -266,9 +295,14 @@ describe('Cloud Security Posture Plugin', () => {
expect(packagePolicyPostCreateCallbacks.length).toBeGreaterThan(0);
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
for (const cb of packagePolicyPostCreateCallbacks) {
const updatedPackagePolicy = await cb(
packageMock,
soClient,
esClient,
contextMock,
httpServerMock.createKibanaRequest()
);
@ -284,6 +318,9 @@ describe('Cloud Security Posture Plugin', () => {
])(
'should uninstall resources when package is removed',
async (total, items, expectedNumberOfCallsToUninstallResources) => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
fleetMock.packagePolicyService.list.mockImplementationOnce(
async (): Promise<ListResult<PackagePolicy>> => {
return {
@ -320,7 +357,7 @@ describe('Cloud Security Posture Plugin', () => {
expect(packagePolicyPostDeleteCallbacks.length).toBeGreaterThan(0);
for (const cb of packagePolicyPostDeleteCallbacks) {
await cb(deletedPackagePolicyMock);
await cb(deletedPackagePolicyMock, soClient, esClient);
}
expect(fleetMock.packagePolicyService.list).toHaveBeenCalledTimes(1);
expect(spy).toHaveBeenCalledTimes(expectedNumberOfCallsToUninstallResources);

View file

@ -6,13 +6,12 @@
*/
import type {
KibanaRequest,
RequestHandlerContext,
PluginInitializerContext,
CoreSetup,
CoreStart,
Plugin,
Logger,
SavedObjectsClientContract,
} from '@kbn/core/server';
import type { DeepReadonly } from 'utility-types';
import type {
@ -107,11 +106,7 @@ export class CspPlugin
plugins.fleet.registerExternalCallback(
'packagePolicyCreate',
async (
packagePolicy: NewPackagePolicy,
_context: RequestHandlerContext,
_request: KibanaRequest
): Promise<NewPackagePolicy> => {
async (packagePolicy: NewPackagePolicy): Promise<NewPackagePolicy> => {
const license = await plugins.licensing.refresh();
if (isCspPackage(packagePolicy.package?.name)) {
if (!isSubscriptionAllowed(this.isCloudEnabled, license)) {
@ -129,12 +124,10 @@ export class CspPlugin
'packagePolicyPostCreate',
async (
packagePolicy: PackagePolicy,
context: RequestHandlerContext,
_: KibanaRequest
soClient: SavedObjectsClientContract
): Promise<PackagePolicy> => {
if (isCspPackage(packagePolicy.package?.name)) {
await this.initialize(core, plugins.taskManager);
const soClient = (await context.core).savedObjects.client;
await onPackagePolicyPostCreateCallback(this.logger, packagePolicy, soClient);
return packagePolicy;

View file

@ -14,16 +14,8 @@ import type { FleetAuthzRouter } from '../../services/security';
import { PACKAGE_POLICY_API_ROUTES } from '../../../common/constants';
import { appContextService, packagePolicyService } from '../../services';
import { createAppContextStartContractMock, xpackMocks } from '../../mocks';
import type {
PackagePolicyClient,
PostPackagePolicyCreateCallback,
PutPackagePolicyUpdateCallback,
FleetRequestHandlerContext,
} from '../..';
import type {
CreatePackagePolicyRequestSchema,
UpdatePackagePolicyRequestSchema,
} from '../../types/rest_spec';
import type { PackagePolicyClient, FleetRequestHandlerContext } from '../..';
import type { UpdatePackagePolicyRequestSchema } from '../../types/rest_spec';
import type { FleetRequestHandler } from '../../types';
import type { PackagePolicy } from '../../types';
@ -125,7 +117,6 @@ describe('When calling package policy', () => {
let routeConfig: RouteConfig<any, any, any, any>;
let context: FleetRequestHandlerContext;
let response: ReturnType<typeof httpServerMock.createResponseFactory>;
let packagePolicyServiceWithAuthzMock: jest.Mocked<PackagePolicyClient>;
beforeEach(() => {
routerMock = httpServiceMock.createRouter() as unknown as jest.Mocked<FleetAuthzRouter>;
@ -135,8 +126,7 @@ describe('When calling package policy', () => {
beforeEach(async () => {
appContextService.start(createAppContextStartContractMock());
context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext;
packagePolicyServiceWithAuthzMock = (await context.fleet).packagePolicyService
.asCurrentUser as jest.Mocked<PackagePolicyClient>;
(await context.fleet).packagePolicyService.asCurrentUser as jest.Mocked<PackagePolicyClient>;
response = httpServerMock.createResponseFactory();
});
@ -145,186 +135,6 @@ describe('When calling package policy', () => {
appContextService.stop();
});
describe('create api handler', () => {
const getCreateKibanaRequest = (
newData?: typeof CreatePackagePolicyRequestSchema.body
): KibanaRequest<undefined, undefined, typeof CreatePackagePolicyRequestSchema.body> => {
return httpServerMock.createKibanaRequest<
undefined,
undefined,
typeof CreatePackagePolicyRequestSchema.body
>({
path: routeConfig.path,
method: 'post',
body: newData || {
name: 'endpoint-1',
description: '',
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
enabled: true,
inputs: [],
namespace: 'default',
package: { name: 'endpoint', title: 'Elastic Endpoint', version: '0.5.0' },
},
});
};
// Set the routeConfig and routeHandler to the Create API
beforeEach(() => {
[routeConfig, routeHandler] = routerMock.post.mock.calls.find(
([{ path }]) => path === PACKAGE_POLICY_API_ROUTES.CREATE_PATTERN
)!;
});
describe('and external callbacks are registered', () => {
const callbackCallingOrder: string[] = [];
// Callback one adds an input that includes a `config` property
const callbackOne: PostPackagePolicyCreateCallback | PutPackagePolicyUpdateCallback = jest.fn(
async (ds) => {
callbackCallingOrder.push('one');
const newDs = {
...ds,
inputs: [
{
type: 'endpoint',
enabled: true,
streams: [],
config: {
one: {
value: 'inserted by callbackOne',
},
},
},
],
};
return newDs;
}
);
// Callback two adds an additional `input[0].config` property
const callbackTwo: PostPackagePolicyCreateCallback | PutPackagePolicyUpdateCallback = jest.fn(
async (ds) => {
callbackCallingOrder.push('two');
const newDs = {
...ds,
inputs: [
{
...ds.inputs[0],
config: {
...ds.inputs[0].config,
two: {
value: 'inserted by callbackTwo',
},
},
},
],
};
return newDs;
}
);
beforeEach(() => {
appContextService.addExternalCallback('packagePolicyCreate', callbackOne);
appContextService.addExternalCallback('packagePolicyCreate', callbackTwo);
});
afterEach(() => (callbackCallingOrder.length = 0));
it('should create with data from callback', async () => {
const request = getCreateKibanaRequest();
packagePolicyServiceMock.runExternalCallbacks.mockImplementationOnce(() =>
Promise.resolve({
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
description: '',
enabled: true,
inputs: [
{
config: {
one: {
value: 'inserted by callbackOne',
},
two: {
value: 'inserted by callbackTwo',
},
},
enabled: true,
streams: [],
type: 'endpoint',
},
],
name: 'endpoint-1',
namespace: 'default',
package: {
name: 'endpoint',
title: 'Elastic Endpoint',
version: '0.5.0',
},
})
);
await routeHandler(context, request, response);
expect(response.ok).toHaveBeenCalled();
expect(packagePolicyServiceWithAuthzMock.create.mock.calls[0][2]).toEqual({
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
description: '',
enabled: true,
inputs: [
{
config: {
one: {
value: 'inserted by callbackOne',
},
two: {
value: 'inserted by callbackTwo',
},
},
enabled: true,
streams: [],
type: 'endpoint',
},
],
name: 'endpoint-1',
namespace: 'default',
package: {
name: 'endpoint',
title: 'Elastic Endpoint',
version: '0.5.0',
},
});
});
});
describe('postCreate callback registration', () => {
it('should call to packagePolicyCreate and packagePolicyPostCreate call backs', async () => {
const request = getCreateKibanaRequest();
await routeHandler(context, request, response);
expect(response.ok).toHaveBeenCalled();
expect(packagePolicyService.runExternalCallbacks).toBeCalledTimes(2);
const firstCB = packagePolicyServiceMock.runExternalCallbacks.mock.calls[0][0];
const secondCB = packagePolicyServiceMock.runExternalCallbacks.mock.calls[1][0];
expect(firstCB).toEqual('packagePolicyCreate');
expect(secondCB).toEqual('packagePolicyPostCreate');
});
it('should not call packagePolicyPostCreate call back in case of packagePolicy create failed', async () => {
const request = getCreateKibanaRequest();
packagePolicyServiceWithAuthzMock.create.mockImplementationOnce(() => {
throw new Error('foo');
});
await routeHandler(context, request, response);
const firstCB = packagePolicyServiceMock.runExternalCallbacks.mock.calls[0][0];
expect(firstCB).toEqual('packagePolicyCreate');
expect(packagePolicyService.runExternalCallbacks).toBeCalledTimes(1);
});
});
});
describe('update api handler', () => {
const getUpdateKibanaRequest = (
newData?: typeof UpdatePackagePolicyRequestSchema.body

View file

@ -247,33 +247,21 @@ export const createPackagePolicyHandler: FleetRequestHandler<
} as NewPackagePolicy);
}
const newData = await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
newPackagePolicy,
context,
request
);
// Create package policy
const packagePolicy = await fleetContext.packagePolicyService.asCurrentUser.create(
soClient,
esClient,
newData,
newPackagePolicy,
{
user,
force,
spaceId,
}
);
const enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks(
'packagePolicyPostCreate',
packagePolicy,
},
context,
request
);
const body: CreatePackagePolicyResponse = { item: enrichedPackagePolicy };
const body: CreatePackagePolicyResponse = { item: packagePolicy };
return response.ok({
body,
@ -368,12 +356,6 @@ export const updatePackagePolicyHandler: FleetRequestHandler<
vars: body.vars ?? packagePolicy.vars,
} as NewPackagePolicy;
}
newData = await packagePolicyService.runExternalCallbacks(
'packagePolicyUpdate',
newData,
context,
request
);
const updatedPackagePolicy = await packagePolicyService.update(
soClient,
@ -400,46 +382,17 @@ export const deletePackagePolicyHandler: RequestHandler<
const soClient = coreContext.savedObjects.client;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
const logger = appContextService.getLogger();
try {
try {
const packagePolicies = await packagePolicyService.getByIDs(
soClient,
request.body.packagePolicyIds,
{ ignoreMissing: true }
);
if (packagePolicies) {
await packagePolicyService.runExternalCallbacks(
'packagePolicyDelete',
packagePolicies,
context,
request
);
}
} catch (error) {
logger.error(`An error occurred executing external callback: ${error}`);
logger.error(error);
}
const body: PostDeletePackagePoliciesResponse = await packagePolicyService.delete(
soClient,
esClient,
request.body.packagePolicyIds,
{ user, force: request.body.force, skipUnassignFromAgentPolicies: request.body.force }
{ user, force: request.body.force, skipUnassignFromAgentPolicies: request.body.force },
context,
request
);
try {
await packagePolicyService.runExternalCallbacks(
'packagePolicyPostDelete',
body,
context,
request
);
} catch (error) {
logger.error(`An error occurred executing external callback: ${error}`);
logger.error(error);
}
return response.ok({
body,
});
@ -457,30 +410,15 @@ export const deleteOnePackagePolicyHandler: RequestHandler<
const soClient = coreContext.savedObjects.client;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const user = appContextService.getSecurity()?.authc.getCurrentUser(request) || undefined;
const logger = appContextService.getLogger();
try {
try {
const packagePolicy = await packagePolicyService.get(
soClient,
request.params.packagePolicyId
);
await packagePolicyService.runExternalCallbacks(
'packagePolicyDelete',
packagePolicy ? [packagePolicy] : [],
context,
request
);
} catch (error) {
logger.error(`An error occurred executing external callback: ${error}`);
logger.error(error);
}
const res = await packagePolicyService.delete(
soClient,
esClient,
[request.params.packagePolicyId],
{ user, force: request.query.force, skipUnassignFromAgentPolicies: request.query.force }
{ user, force: request.query.force, skipUnassignFromAgentPolicies: request.query.force },
context,
request
);
if (
@ -493,17 +431,7 @@ export const deleteOnePackagePolicyHandler: RequestHandler<
body: res[0].body,
});
}
try {
await packagePolicyService.runExternalCallbacks(
'packagePolicyPostDelete',
res,
context,
request
);
} catch (error) {
logger.error(`An error occurred executing external callback: ${error}`);
logger.error(error);
}
return response.ok({
body: { id: request.params.packagePolicyId },
});

View file

@ -169,13 +169,6 @@ describe('agent policy', () => {
]);
});
it('should run package policy delete external callbacks', async () => {
await agentPolicyService.delete(soClient, esClient, 'mocked');
expect(packagePolicyService.runPostDeleteExternalCallbacks).toHaveBeenCalledWith([
{ id: 'package-1' },
]);
});
it('should throw error for agent policy which has managed package poolicy', async () => {
mockedPackagePolicyService.findAllForAgentPolicy.mockReturnValue([
{
@ -192,12 +185,6 @@ describe('agent policy', () => {
).message
);
}
await agentPolicyService.delete(soClient, esClient, 'mocked', { force: true });
expect(packagePolicyService.runPostDeleteExternalCallbacks).toHaveBeenCalledWith([
{ id: 'package-1' },
]);
});
});

View file

@ -51,7 +51,6 @@ import type {
FleetServerPolicy,
Installation,
Output,
PostDeletePackagePoliciesResponse,
PackageInfo,
} from '../../common/types';
import {
@ -681,25 +680,15 @@ class AgentPolicyService {
);
}
await packagePolicyService.runDeleteExternalCallbacks(packagePolicies);
const deletedPackagePolicies: PostDeletePackagePoliciesResponse =
await packagePolicyService.delete(
soClient,
esClient,
packagePolicies.map((p) => p.id),
{
force: options?.force,
skipUnassignFromAgentPolicies: true,
}
);
try {
await packagePolicyService.runPostDeleteExternalCallbacks(deletedPackagePolicies);
} catch (error) {
const logger = appContextService.getLogger();
logger.error(`An error occurred executing external callback: ${error}`);
logger.error(error);
}
await packagePolicyService.delete(
soClient,
esClient,
packagePolicies.map((p) => p.id),
{
force: options?.force,
skipUnassignFromAgentPolicies: true,
}
);
}
if (agentPolicy.is_preconfigured && !options?.force) {

View file

@ -2132,10 +2132,10 @@ describe('Package policy service', () => {
{ id: 'a', success: true },
{ id: 'a', success: true },
];
callbackOne = jest.fn(async (deletedPolicies) => {
callbackOne = jest.fn(async (deletedPolicies, soClient, esClient) => {
callingOrder.push('one');
});
callbackTwo = jest.fn(async (deletedPolicies) => {
callbackTwo = jest.fn(async (deletedPolicies, soClient, esClient) => {
callingOrder.push('two');
});
appContextService.addExternalCallback('packagePolicyPostDelete', callbackOne);
@ -2147,25 +2147,54 @@ describe('Package policy service', () => {
});
it('should execute external callbacks', async () => {
await packagePolicyService.runPostDeleteExternalCallbacks(deletedPackagePolicies);
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
expect(callbackOne).toHaveBeenCalledWith(deletedPackagePolicies);
expect(callbackTwo).toHaveBeenCalledWith(deletedPackagePolicies);
await packagePolicyService.runPostDeleteExternalCallbacks(
deletedPackagePolicies,
soClient,
esClient
);
expect(callbackOne).toHaveBeenCalledWith(
deletedPackagePolicies,
expect.any(Object),
expect.any(Object),
undefined,
undefined
);
expect(callbackTwo).toHaveBeenCalledWith(
deletedPackagePolicies,
expect.any(Object),
expect.any(Object),
undefined,
undefined
);
expect(callingOrder).toEqual(['one', 'two']);
});
it("should execute all external callbacks even if one throw's", async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
callbackOne.mockImplementation(async (deletedPolicies) => {
callingOrder.push('one');
throw new Error('foo');
});
await expect(
packagePolicyService.runPostDeleteExternalCallbacks(deletedPackagePolicies)
packagePolicyService.runPostDeleteExternalCallbacks(
deletedPackagePolicies,
soClient,
esClient
)
).rejects.toThrow(FleetError);
expect(callingOrder).toEqual(['one', 'two']);
});
it('should provide an array of errors encountered by running external callbacks', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
let error: FleetError;
const callbackOneError = new Error('foo 1');
const callbackTwoError = new Error('foo 2');
@ -2180,7 +2209,7 @@ describe('Package policy service', () => {
});
await packagePolicyService
.runPostDeleteExternalCallbacks(deletedPackagePolicies)
.runPostDeleteExternalCallbacks(deletedPackagePolicies, soClient, esClient)
.catch((e) => {
error = e;
});
@ -2203,10 +2232,10 @@ describe('Package policy service', () => {
appContextService.start(createAppContextStartContractMock());
callingOrder = [];
packagePolicies = [{ id: 'a' }, { id: 'a' }] as DeletePackagePoliciesResponse;
callbackOne = jest.fn(async (deletedPolicies) => {
callbackOne = jest.fn(async (deletedPolicies, soClient, esClient) => {
callingOrder.push('one');
});
callbackTwo = jest.fn(async (deletedPolicies) => {
callbackTwo = jest.fn(async (deletedPolicies, soClient, esClient) => {
callingOrder.push('two');
});
appContextService.addExternalCallback('packagePolicyDelete', callbackOne);
@ -2218,25 +2247,31 @@ describe('Package policy service', () => {
});
it('should execute external callbacks', async () => {
await packagePolicyService.runDeleteExternalCallbacks(packagePolicies);
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
await packagePolicyService.runDeleteExternalCallbacks(packagePolicies, soClient, esClient);
expect(callbackOne).toHaveBeenCalledWith(packagePolicies);
expect(callbackTwo).toHaveBeenCalledWith(packagePolicies);
expect(callbackOne).toHaveBeenCalledWith(packagePolicies, soClient, esClient);
expect(callbackTwo).toHaveBeenCalledWith(packagePolicies, soClient, esClient);
expect(callingOrder).toEqual(['one', 'two']);
});
it("should execute all external callbacks even if one throw's", async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
callbackOne.mockImplementation(async (deletedPolicies) => {
callingOrder.push('one');
throw new Error('foo');
});
await expect(
packagePolicyService.runDeleteExternalCallbacks(packagePolicies)
packagePolicyService.runDeleteExternalCallbacks(packagePolicies, soClient, esClient)
).rejects.toThrow(FleetError);
expect(callingOrder).toEqual(['one', 'two']);
});
it('should provide an array of errors encountered by running external callbacks', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
let error: FleetError;
const callbackOneError = new Error('foo 1');
const callbackTwoError = new Error('foo 2');
@ -2250,9 +2285,11 @@ describe('Package policy service', () => {
throw callbackTwoError;
});
await packagePolicyService.runDeleteExternalCallbacks(packagePolicies).catch((e) => {
error = e;
});
await packagePolicyService
.runDeleteExternalCallbacks(packagePolicies, soClient, esClient)
.catch((e) => {
error = e;
});
expect(error!.message).toEqual(
'2 encountered while executing package delete external callbacks'
@ -2334,6 +2371,9 @@ describe('Package policy service', () => {
});
it('should call external callbacks in expected order', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const callbackA: CombinedExternalCallback = jest.fn(async (ds) => {
callbackCallingOrder.push('a');
return ds;
@ -2350,6 +2390,8 @@ describe('Package policy service', () => {
await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
newPackagePolicy,
soClient,
esClient,
coreMock.createCustomRequestHandlerContext(context),
request
);
@ -2357,12 +2399,17 @@ describe('Package policy service', () => {
});
it('should feed package policy returned by last callback', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
appContextService.addExternalCallback('packagePolicyCreate', callbackOne);
appContextService.addExternalCallback('packagePolicyCreate', callbackTwo);
await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
newPackagePolicy,
soClient,
esClient,
coreMock.createCustomRequestHandlerContext(context),
request
);
@ -2406,10 +2453,15 @@ describe('Package policy service', () => {
});
it('should fail to execute remaining callbacks after a callback exception', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
try {
await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
newPackagePolicy,
soClient,
esClient,
coreMock.createCustomRequestHandlerContext(context),
request
);
@ -2425,10 +2477,14 @@ describe('Package policy service', () => {
});
it('should fail to return the package policy', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
expect(
packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
newPackagePolicy,
soClient,
esClient,
coreMock.createCustomRequestHandlerContext(context),
request
)
@ -2504,6 +2560,9 @@ describe('Package policy service', () => {
});
it('should execute PostPackagePolicyPostCreateCallback external callbacks', async () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const callbackA: PostPackagePolicyPostCreateCallback = jest.fn(async (ds) => {
callbackCallingOrder.push('a');
return ds;
@ -2521,12 +2580,26 @@ describe('Package policy service', () => {
await packagePolicyService.runExternalCallbacks(
'packagePolicyPostCreate',
packagePolicy,
soClient,
esClient,
requestContext,
request
);
expect(callbackA).toHaveBeenCalledWith(packagePolicy, requestContext, request);
expect(callbackB).toHaveBeenCalledWith(packagePolicy, requestContext, request);
expect(callbackA).toHaveBeenCalledWith(
packagePolicy,
soClient,
esClient,
requestContext,
request
);
expect(callbackB).toHaveBeenCalledWith(
packagePolicy,
soClient,
esClient,
requestContext,
request
);
expect(callbackCallingOrder).toEqual(['a', 'b']);
});
});

View file

@ -13,9 +13,9 @@ import { getFlattenedObject } from '@kbn/std';
import type {
KibanaRequest,
ElasticsearchClient,
RequestHandlerContext,
SavedObjectsClientContract,
Logger,
RequestHandlerContext,
} from '@kbn/core/server';
import { v4 as uuidv4 } from 'uuid';
import { safeLoad } from 'js-yaml';
@ -133,31 +133,47 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
skipUniqueNameVerification?: boolean;
overwrite?: boolean;
packageInfo?: PackageInfo;
}
},
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<PackagePolicy> {
const logger = appContextService.getLogger();
const agentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id, true);
if (agentPolicy && packagePolicy.package?.name === FLEET_APM_PACKAGE) {
const enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks(
'packagePolicyCreate',
packagePolicy,
soClient,
esClient,
context,
request
);
const agentPolicy = await agentPolicyService.get(
soClient,
enrichedPackagePolicy.policy_id,
true
);
if (agentPolicy && enrichedPackagePolicy.package?.name === FLEET_APM_PACKAGE) {
const dataOutput = await getDataOutputForAgentPolicy(soClient, agentPolicy);
if (dataOutput.type === outputType.Logstash) {
throw new FleetError('You cannot add APM to a policy using a logstash output');
}
}
await validateIsNotHostedPolicy(soClient, packagePolicy.policy_id, options?.force);
await validateIsNotHostedPolicy(soClient, enrichedPackagePolicy.policy_id, options?.force);
// trailing whitespace causes issues creating API keys
packagePolicy.name = packagePolicy.name.trim();
enrichedPackagePolicy.name = enrichedPackagePolicy.name.trim();
if (!options?.skipUniqueNameVerification) {
const existingPoliciesWithName = await this.list(soClient, {
perPage: 1,
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${packagePolicy.name}"`,
kuery: `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.name: "${enrichedPackagePolicy.name}"`,
});
// Check that the name does not exist already
if (existingPoliciesWithName.items.length > 0) {
throw new FleetError(
`An integration policy with the name ${packagePolicy.name} already exists. Please rename it or choose a different name.`
`An integration policy with the name ${enrichedPackagePolicy.name} already exists. Please rename it or choose a different name.`
);
}
}
@ -165,32 +181,36 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
let elasticsearchPrivileges: NonNullable<PackagePolicy['elasticsearch']>['privileges'];
// Add ids to stream
const packagePolicyId = options?.id || uuidv4();
let inputs: PackagePolicyInput[] = packagePolicy.inputs.map((input) =>
let inputs: PackagePolicyInput[] = enrichedPackagePolicy.inputs.map((input) =>
assignStreamIdToInput(packagePolicyId, input)
);
// Make sure the associated package is installed
if (packagePolicy.package?.name) {
if (enrichedPackagePolicy.package?.name) {
if (!options?.skipEnsureInstalled) {
await ensureInstalledPackage({
esClient,
spaceId: options?.spaceId || DEFAULT_SPACE_ID,
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
pkgVersion: packagePolicy.package.version,
pkgName: enrichedPackagePolicy.package.name,
pkgVersion: enrichedPackagePolicy.package.version,
force: options?.force,
});
}
// Handle component template/mappings updates for experimental features, e.g. synthetic source
await handleExperimentalDatastreamFeatureOptIn({ soClient, esClient, packagePolicy });
await handleExperimentalDatastreamFeatureOptIn({
soClient,
esClient,
packagePolicy: enrichedPackagePolicy,
});
const pkgInfo =
options?.packageInfo ??
(await getPackageInfo({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
pkgVersion: packagePolicy.package.version,
pkgName: enrichedPackagePolicy.package.name,
pkgVersion: enrichedPackagePolicy.package.version,
prerelease: true,
}));
@ -203,9 +223,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
);
}
}
validatePackagePolicyOrThrow(packagePolicy, pkgInfo);
validatePackagePolicyOrThrow(enrichedPackagePolicy, pkgInfo);
inputs = await _compilePackagePolicyInputs(pkgInfo, packagePolicy.vars || {}, inputs);
inputs = await _compilePackagePolicyInputs(pkgInfo, enrichedPackagePolicy.vars || {}, inputs);
elasticsearchPrivileges = pkgInfo.elasticsearch?.privileges;
@ -214,7 +234,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
soClient,
esClient,
pkgInfo,
packagePolicy,
packagePolicy: enrichedPackagePolicy,
force: !!options?.force,
logger,
});
@ -225,9 +245,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
const newSo = await soClient.create<PackagePolicySOAttributes>(
SAVED_OBJECT_TYPE,
{
...packagePolicy,
...(packagePolicy.package
? { package: omit(packagePolicy.package, 'experimental_data_stream_features') }
...enrichedPackagePolicy,
...(enrichedPackagePolicy.package
? { package: omit(enrichedPackagePolicy.package, 'experimental_data_stream_features') }
: {}),
inputs,
...(elasticsearchPrivileges && { elasticsearch: { privileges: elasticsearchPrivileges } }),
@ -242,16 +262,18 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
);
if (options?.bumpRevision ?? true) {
await agentPolicyService.bumpRevision(soClient, esClient, packagePolicy.policy_id, {
await agentPolicyService.bumpRevision(soClient, esClient, enrichedPackagePolicy.policy_id, {
user: options?.user,
});
}
return {
id: newSo.id,
version: newSo.version,
...newSo.attributes,
};
const createdPackagePolicy = { id: newSo.id, version: newSo.version, ...newSo.attributes };
return packagePolicyService.runExternalCallbacks(
'packagePolicyPostCreate',
createdPackagePolicy,
soClient,
esClient
);
}
public async bulkCreate(
@ -494,7 +516,23 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
options?: { user?: AuthenticatedUser; force?: boolean; skipUniqueNameVerification?: boolean },
currentVersion?: string
): Promise<PackagePolicy> {
const packagePolicy = { ...packagePolicyUpdate, name: packagePolicyUpdate.name.trim() };
let enrichedPackagePolicy: UpdatePackagePolicy;
try {
enrichedPackagePolicy = await packagePolicyService.runExternalCallbacks(
'packagePolicyUpdate',
packagePolicyUpdate,
soClient,
esClient
);
} catch (error) {
const logger = appContextService.getLogger();
logger.error(`An error occurred executing "packagePolicyUpdate" callback: ${error}`);
logger.error(error);
enrichedPackagePolicy = packagePolicyUpdate;
}
const packagePolicy = { ...enrichedPackagePolicy, name: enrichedPackagePolicy.name.trim() };
const oldPackagePolicy = await this.get(soClient, id);
const { version, ...restOfPackagePolicy } = packagePolicy;
@ -714,15 +752,35 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
ids: string[],
options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean; force?: boolean }
options?: {
user?: AuthenticatedUser;
skipUnassignFromAgentPolicies?: boolean;
force?: boolean;
},
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<PostDeletePackagePoliciesResponse> {
const result: PostDeletePackagePoliciesResponse = [];
const logger = appContextService.getLogger();
const packagePolicies = await this.getByIDs(soClient, ids, { ignoreMissing: true });
if (!packagePolicies) {
return [];
}
try {
await packagePolicyService.runDeleteExternalCallbacks(
packagePolicies,
soClient,
esClient,
context,
request
);
} catch (error) {
logger.error(`An error occurred executing "packagePolicyDelete" callback: ${error}`);
logger.error(error);
}
const uniqueAgentPolicyIds = [
...new Set(packagePolicies.map((packagePolicy) => packagePolicy.policy_id)),
];
@ -823,6 +881,19 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
}
try {
await packagePolicyService.runPostDeleteExternalCallbacks(
result,
soClient,
esClient,
context,
request
);
} catch (error) {
logger.error(`An error occurred executing "packagePolicyPostDelete" callback: ${error}`);
logger.error(error);
}
return result;
}
@ -1235,9 +1306,13 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
? PostDeletePackagePoliciesResponse
: A extends 'packagePolicyPostCreate'
? PackagePolicy
: NewPackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
: A extends 'packagePolicyCreate'
? NewPackagePolicy
: never,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<
A extends 'packagePolicyDelete'
? void
@ -1245,7 +1320,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
? void
: A extends 'packagePolicyPostCreate'
? PackagePolicy
: NewPackagePolicy
: A extends 'packagePolicyCreate'
? NewPackagePolicy
: never
>;
public async runExternalCallbacks(
externalCallbackType: ExternalCallback[0],
@ -1254,53 +1331,95 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
| NewPackagePolicy
| PostDeletePackagePoliciesResponse
| DeletePackagePoliciesResponse,
context: RequestHandlerContext,
request: KibanaRequest
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<PackagePolicy | NewPackagePolicy | void> {
if (externalCallbackType === 'packagePolicyPostDelete') {
return await this.runPostDeleteExternalCallbacks(
packagePolicy as PostDeletePackagePoliciesResponse
);
} else if (externalCallbackType === 'packagePolicyDelete') {
return await this.runDeleteExternalCallbacks(packagePolicy as DeletePackagePoliciesResponse);
} else {
if (!Array.isArray(packagePolicy)) {
let newData = packagePolicy;
const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType);
if (externalCallbacks && externalCallbacks.size > 0) {
let updatedNewData = newData;
for (const callback of externalCallbacks) {
let result;
if (externalCallbackType === 'packagePolicyPostCreate') {
result = await (callback as PostPackagePolicyPostCreateCallback)(
updatedNewData as PackagePolicy,
context,
request
);
updatedNewData = PackagePolicySchema.validate(result);
} else {
result = await (callback as PostPackagePolicyCreateCallback)(
updatedNewData as NewPackagePolicy,
context,
request
);
}
if (externalCallbackType === 'packagePolicyCreate') {
updatedNewData = NewPackagePolicySchema.validate(result);
} else if (externalCallbackType === 'packagePolicyUpdate') {
updatedNewData = UpdatePackagePolicySchema.validate(result);
}
}
const logger = appContextService.getLogger();
const numberOfCallbacks = appContextService.getExternalCallbacks(externalCallbackType)?.size;
logger.debug(`Running ${numberOfCallbacks} external callbacks for ${externalCallbackType}`);
try {
if (externalCallbackType === 'packagePolicyPostDelete') {
return await this.runPostDeleteExternalCallbacks(
packagePolicy as PostDeletePackagePoliciesResponse,
soClient,
esClient,
context,
request
);
} else if (externalCallbackType === 'packagePolicyDelete') {
return await this.runDeleteExternalCallbacks(
packagePolicy as DeletePackagePoliciesResponse,
soClient,
esClient
);
} else {
if (!Array.isArray(packagePolicy)) {
let newData = packagePolicy;
const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType);
if (externalCallbacks && externalCallbacks.size > 0) {
let updatedNewData = newData;
for (const callback of externalCallbacks) {
let result;
if (externalCallbackType === 'packagePolicyPostCreate') {
result = await (callback as PostPackagePolicyPostCreateCallback)(
updatedNewData as PackagePolicy,
soClient,
esClient,
context,
request
);
updatedNewData = PackagePolicySchema.validate(result);
} else {
result = await (callback as PostPackagePolicyCreateCallback)(
updatedNewData as NewPackagePolicy,
soClient,
esClient,
context,
request
);
}
newData = updatedNewData;
if (externalCallbackType === 'packagePolicyCreate') {
updatedNewData = NewPackagePolicySchema.validate(result);
} else if (externalCallbackType === 'packagePolicyUpdate') {
const omitted = {
...omit(result, [
'id',
'version',
'revision',
'updated_at',
'updated_by',
'created_at',
'created_by',
'elasticsearch',
]),
inputs: result.inputs.map((input) => omit(input, ['compiled_input'])),
};
updatedNewData = UpdatePackagePolicySchema.validate(omitted);
}
}
newData = updatedNewData;
}
return newData;
}
return newData;
}
} catch (error) {
logger.error(`Error running external callbacks for ${externalCallbackType}:`);
logger.error(error);
throw error;
}
}
public async runPostDeleteExternalCallbacks(
deletedPackagePolicies: PostDeletePackagePoliciesResponse
deletedPackagePolicies: PostDeletePackagePoliciesResponse,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<void> {
const externalCallbacks = appContextService.getExternalCallbacks('packagePolicyPostDelete');
const errorsThrown: Error[] = [];
@ -1310,7 +1429,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
// Failures from an external callback should not prevent other external callbacks from being
// executed. Errors (if any) will be collected and `throw`n after processing the entire set
try {
await callback(deletedPackagePolicies);
await callback(deletedPackagePolicies, soClient, esClient, context, request);
} catch (error) {
errorsThrown.push(error);
}
@ -1326,7 +1445,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
public async runDeleteExternalCallbacks(
deletedPackagePolices: DeletePackagePoliciesResponse
deletedPackagePolices: DeletePackagePoliciesResponse,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient
): Promise<void> {
const externalCallbacks = appContextService.getExternalCallbacks('packagePolicyDelete');
const errorsThrown: Error[] = [];
@ -1336,7 +1457,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
// Failures from an external callback should not prevent other external callbacks from being
// executed. Errors (if any) will be collected and `throw`n after processing the entire set
try {
await callback(deletedPackagePolices);
await callback(deletedPackagePolices, soClient, esClient);
} catch (error) {
errorsThrown.push(error);
}
@ -1402,7 +1523,9 @@ class PackagePolicyClientWithAuthz extends PackagePolicyClientImpl {
skipUniqueNameVerification?: boolean;
overwrite?: boolean;
packageInfo?: PackageInfo;
}
},
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<PackagePolicy> {
await this.#runPreflight({
fleetAuthz: {
@ -1410,7 +1533,7 @@ class PackagePolicyClientWithAuthz extends PackagePolicyClientImpl {
},
});
return super.create(soClient, esClient, packagePolicy, options);
return super.create(soClient, esClient, packagePolicy, options, context, request);
}
}

View file

@ -5,12 +5,8 @@
* 2.0.
*/
import type { KibanaRequest, Logger } from '@kbn/core/server';
import type {
ElasticsearchClient,
RequestHandlerContext,
SavedObjectsClientContract,
} from '@kbn/core/server';
import type { KibanaRequest, Logger, RequestHandlerContext } from '@kbn/core/server';
import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import type { AuthenticatedUser } from '@kbn/security-plugin/server';
import type {
@ -50,7 +46,9 @@ export interface PackagePolicyClient {
skipUniqueNameVerification?: boolean;
overwrite?: boolean;
packageInfo?: PackageInfo;
}
},
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<PackagePolicy>;
bulkCreate(
@ -108,7 +106,13 @@ export interface PackagePolicyClient {
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
ids: string[],
options?: { user?: AuthenticatedUser; skipUnassignFromAgentPolicies?: boolean; force?: boolean }
options?: {
user?: AuthenticatedUser;
skipUnassignFromAgentPolicies?: boolean;
force?: boolean;
},
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<PostDeletePackagePoliciesResponse>;
upgrade(
@ -146,9 +150,13 @@ export interface PackagePolicyClient {
? PostDeletePackagePoliciesResponse
: A extends 'packagePolicyPostCreate'
? PackagePolicy
: A extends 'packagePolicyUpdate'
? UpdatePackagePolicy
: NewPackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<
A extends 'packagePolicyDelete'
? void
@ -156,13 +164,25 @@ export interface PackagePolicyClient {
? void
: A extends 'packagePolicyPostCreate'
? PackagePolicy
: A extends 'packagePolicyUpdate'
? UpdatePackagePolicy
: NewPackagePolicy
>;
runDeleteExternalCallbacks(deletedPackagePolicies: DeletePackagePoliciesResponse): Promise<void>;
runDeleteExternalCallbacks(
deletedPackagePolicies: DeletePackagePoliciesResponse,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<void>;
runPostDeleteExternalCallbacks(
deletedPackagePolicies: PostDeletePackagePoliciesResponse
deletedPackagePolicies: PostDeletePackagePoliciesResponse,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
): Promise<void>;
getUpgradePackagePolicyInfo(

View file

@ -6,6 +6,7 @@
*/
import type { KibanaRequest, RequestHandlerContext } from '@kbn/core/server';
import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import type { DeepReadonly } from 'utility-types';
@ -18,29 +19,43 @@ import type {
} from '../../common/types';
export type PostPackagePolicyDeleteCallback = (
packagePolicies: DeletePackagePoliciesResponse
packagePolicies: DeletePackagePoliciesResponse,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
) => Promise<void>;
export type PostPackagePolicyPostDeleteCallback = (
deletedPackagePolicies: DeepReadonly<PostDeletePackagePoliciesResponse>
deletedPackagePolicies: DeepReadonly<PostDeletePackagePoliciesResponse>,
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
) => Promise<void>;
export type PostPackagePolicyCreateCallback = (
newPackagePolicy: NewPackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
) => Promise<NewPackagePolicy>;
export type PostPackagePolicyPostCreateCallback = (
packagePolicy: PackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
) => Promise<PackagePolicy>;
export type PutPackagePolicyUpdateCallback = (
updatePackagePolicy: UpdatePackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
context?: RequestHandlerContext,
request?: KibanaRequest
) => Promise<UpdatePackagePolicy>;
export type ExternalCallbackCreate = ['packagePolicyCreate', PostPackagePolicyCreateCallback];

View file

@ -7,7 +7,12 @@
import type { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types';
import { httpServerMock, loggingSystemMock } from '@kbn/core/server/mocks';
import {
elasticsearchServiceMock,
httpServerMock,
loggingSystemMock,
savedObjectsClientMock,
} from '@kbn/core/server/mocks';
import {
createNewPackagePolicyMock,
deletePackagePolicyMock,
@ -84,6 +89,9 @@ describe('ingest_integration tests ', () => {
});
describe('package policy init callback (atifacts manifest initialisation tests)', () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const createNewEndpointPolicyInput = (manifest: ManifestSchema) => ({
type: 'endpoint',
enabled: true,
@ -106,7 +114,13 @@ describe('ingest_integration tests ', () => {
exceptionListClient
);
return callback(createNewPackagePolicyMock(), requestContextMock.convertContext(ctx), req);
return callback(
createNewPackagePolicyMock(),
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
};
const TEST_POLICY_ID_1 = 'c6d16e42-c32d-4dce-8a88-113cfe276ad1';
@ -258,6 +272,8 @@ describe('ingest_integration tests ', () => {
});
describe('package policy post create callback', () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const logger = loggingSystemMock.create().get('ingest_integration.test');
const callback = getPackagePolicyPostCreateCallback(logger, exceptionListClient);
const policyConfig = generator.generatePolicyPackagePolicy() as PackagePolicy;
@ -275,6 +291,8 @@ describe('ingest_integration tests ', () => {
};
const postCreatedPolicyConfig = await callback(
policyConfig,
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
@ -312,6 +330,8 @@ describe('ingest_integration tests ', () => {
};
const postCreatedPolicyConfig = await callback(
policyConfig,
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
@ -326,6 +346,9 @@ describe('ingest_integration tests ', () => {
});
});
describe('package policy update callback (when the license is below platinum)', () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
beforeEach(() => {
licenseEmitter.next(Gold); // set license level to gold
});
@ -341,7 +364,7 @@ describe('ingest_integration tests ', () => {
const policyConfig = generator.generatePolicyPackagePolicy();
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
await expect(() =>
callback(policyConfig, requestContextMock.convertContext(ctx), req)
callback(policyConfig, soClient, esClient, requestContextMock.convertContext(ctx), req)
).rejects.toThrow('Requires Platinum license');
});
it('updates successfully if no paid features are turned on in the policy', async () => {
@ -358,6 +381,8 @@ describe('ingest_integration tests ', () => {
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
const updatedPolicyConfig = await callback(
policyConfig,
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
@ -366,6 +391,9 @@ describe('ingest_integration tests ', () => {
});
describe('package policy update callback (when the license is at least platinum)', () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
beforeEach(() => {
licenseEmitter.next(Platinum); // set license level to platinum
});
@ -383,6 +411,8 @@ describe('ingest_integration tests ', () => {
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
const updatedPolicyConfig = await callback(
policyConfig,
soClient,
esClient,
requestContextMock.convertContext(ctx),
req
);
@ -391,9 +421,12 @@ describe('ingest_integration tests ', () => {
});
describe('package policy delete callback', () => {
const soClient = savedObjectsClientMock.create();
const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser;
const invokeDeleteCallback = async (): Promise<void> => {
const callback = getPackagePolicyDeleteCallback(exceptionListClient);
await callback(deletePackagePolicyMock());
await callback(deletePackagePolicyMock(), soClient, esClient);
};
let removedPolicies: PostDeletePackagePoliciesResponse;

View file

@ -5,7 +5,7 @@
* 2.0.
*/
import type { KibanaRequest, Logger, RequestHandlerContext } from '@kbn/core/server';
import type { Logger } from '@kbn/core/server';
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
import type { PluginStartContract as AlertsStartContract } from '@kbn/alerting-plugin/server';
import type {
@ -55,10 +55,18 @@ export const getPackagePolicyCreateCallback = (
exceptionsClient: ExceptionListClient | undefined
): PostPackagePolicyCreateCallback => {
return async (
newPackagePolicy: NewPackagePolicy,
context: RequestHandlerContext,
request: KibanaRequest
newPackagePolicy,
soClient,
esClient,
context,
request
): Promise<NewPackagePolicy> => {
// callback is called outside request context
if (!context || !request) {
logger.debug('PackagePolicyCreateCallback called outside request context. Skipping...');
return newPackagePolicy;
}
// We only care about Endpoint package policies
if (!isEndpointPackagePolicy(newPackagePolicy)) {
return newPackagePolicy;
@ -140,11 +148,7 @@ export const getPackagePolicyUpdateCallback = (
featureUsageService: FeatureUsageService,
endpointMetadataService: EndpointMetadataService
): PutPackagePolicyUpdateCallback => {
return async (
newPackagePolicy: NewPackagePolicy
// context: RequestHandlerContext,
// request: KibanaRequest
): Promise<UpdatePackagePolicy> => {
return async (newPackagePolicy: NewPackagePolicy): Promise<UpdatePackagePolicy> => {
if (!isEndpointPackagePolicy(newPackagePolicy)) {
return newPackagePolicy;
}
@ -174,7 +178,7 @@ export const getPackagePolicyPostCreateCallback = (
return packagePolicy;
}
const integrationConfig = packagePolicy?.inputs[0].config?.integration_config;
const integrationConfig = packagePolicy?.inputs[0]?.config?.integration_config;
if (integrationConfig && integrationConfig?.value?.eventFilters !== undefined) {
createEventFilters(