[Security Solution][Endpoint][Admin] Api guard for paid policy features (#84667)
This commit is contained in:
parent
c0f9de1f5a
commit
4e4e55029c
|
@ -34,6 +34,7 @@ export const createPackagePolicyServiceMock = () => {
|
|||
getByIDs: jest.fn(),
|
||||
list: jest.fn(),
|
||||
update: jest.fn(),
|
||||
runExternalCallbacks: jest.fn(),
|
||||
} as jest.Mocked<PackagePolicyServiceInterface>;
|
||||
};
|
||||
|
||||
|
|
|
@ -52,7 +52,12 @@ import {
|
|||
registerSettingsRoutes,
|
||||
registerAppRoutes,
|
||||
} from './routes';
|
||||
import { EsAssetReference, FleetConfigType, NewPackagePolicy } from '../common';
|
||||
import {
|
||||
EsAssetReference,
|
||||
FleetConfigType,
|
||||
NewPackagePolicy,
|
||||
UpdatePackagePolicy,
|
||||
} from '../common';
|
||||
import {
|
||||
appContextService,
|
||||
licenseService,
|
||||
|
@ -119,14 +124,23 @@ const allSavedObjectTypes = [
|
|||
/**
|
||||
* Callbacks supported by the Fleet plugin
|
||||
*/
|
||||
export type ExternalCallback = [
|
||||
'packagePolicyCreate',
|
||||
(
|
||||
newPackagePolicy: NewPackagePolicy,
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
) => Promise<NewPackagePolicy>
|
||||
];
|
||||
export type ExternalCallback =
|
||||
| [
|
||||
'packagePolicyCreate',
|
||||
(
|
||||
newPackagePolicy: NewPackagePolicy,
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
) => Promise<NewPackagePolicy>
|
||||
]
|
||||
| [
|
||||
'packagePolicyUpdate',
|
||||
(
|
||||
newPackagePolicy: UpdatePackagePolicy,
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
) => Promise<UpdatePackagePolicy>
|
||||
];
|
||||
|
||||
export type ExternalCallbacksStorage = Map<ExternalCallback[0], Set<ExternalCallback[1]>>;
|
||||
|
||||
|
@ -302,8 +316,8 @@ export class FleetPlugin
|
|||
getFullAgentPolicy: agentPolicyService.getFullAgentPolicy,
|
||||
},
|
||||
packagePolicyService,
|
||||
registerExternalCallback: (...args: ExternalCallback) => {
|
||||
return appContextService.addExternalCallback(...args);
|
||||
registerExternalCallback: (type: ExternalCallback[0], callback: ExternalCallback[1]) => {
|
||||
return appContextService.addExternalCallback(type, callback);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { httpServerMock, httpServiceMock } from 'src/core/server/mocks';
|
||||
import { IRouter, KibanaRequest, Logger, RequestHandler, RouteConfig } from 'kibana/server';
|
||||
import { IRouter, KibanaRequest, RequestHandler, RouteConfig } from 'kibana/server';
|
||||
import { registerRoutes } from './index';
|
||||
import { PACKAGE_POLICY_API_ROUTES } from '../../../common/constants';
|
||||
import { xpackMocks } from '../../../../../mocks';
|
||||
|
@ -48,6 +48,9 @@ jest.mock('../../services/package_policy', (): {
|
|||
getByIDs: jest.fn(),
|
||||
list: jest.fn(),
|
||||
update: jest.fn(),
|
||||
runExternalCallbacks: jest.fn((callbackType, newPackagePolicy, context, request) =>
|
||||
Promise.resolve(newPackagePolicy)
|
||||
),
|
||||
},
|
||||
};
|
||||
});
|
||||
|
@ -164,50 +167,26 @@ describe('When calling package policy', () => {
|
|||
|
||||
afterEach(() => (callbackCallingOrder.length = 0));
|
||||
|
||||
it('should call external callbacks in expected order', async () => {
|
||||
it('should create with data from callback', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(callbackCallingOrder).toEqual(['one', 'two']);
|
||||
});
|
||||
|
||||
it('should feed package policy returned by last callback', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(callbackOne).toHaveBeenCalledWith(
|
||||
{
|
||||
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
},
|
||||
context,
|
||||
request
|
||||
);
|
||||
expect(callbackTwo).toHaveBeenCalledWith(
|
||||
{
|
||||
packagePolicyServiceMock.runExternalCallbacks.mockImplementationOnce(() =>
|
||||
Promise.resolve({
|
||||
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
one: {
|
||||
value: 'inserted by callbackOne',
|
||||
},
|
||||
two: {
|
||||
value: 'inserted by callbackTwo',
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
streams: [],
|
||||
type: 'endpoint',
|
||||
},
|
||||
],
|
||||
name: 'endpoint-1',
|
||||
|
@ -218,14 +197,8 @@ describe('When calling package policy', () => {
|
|||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
},
|
||||
context,
|
||||
request
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should create with data from callback', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(packagePolicyServiceMock.create.mock.calls[0][2]).toEqual({
|
||||
|
@ -257,91 +230,6 @@ describe('When calling package policy', () => {
|
|||
},
|
||||
});
|
||||
});
|
||||
|
||||
describe('and a callback throws an exception', () => {
|
||||
const callbackThree: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('three');
|
||||
throw new Error('callbackThree threw error on purpose');
|
||||
});
|
||||
|
||||
const callbackFour: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('four');
|
||||
return {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
...ds.inputs[0],
|
||||
config: {
|
||||
...ds.inputs[0].config,
|
||||
four: {
|
||||
value: 'inserted by callbackFour',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackThree);
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackFour);
|
||||
});
|
||||
|
||||
it('should skip over callback exceptions and still execute other callbacks', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(callbackCallingOrder).toEqual(['one', 'two', 'three', 'four']);
|
||||
});
|
||||
|
||||
it('should log errors', async () => {
|
||||
const errorLogger = (appContextService.getLogger() as jest.Mocked<Logger>).error;
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(errorLogger.mock.calls).toEqual([
|
||||
['An external registered [packagePolicyCreate] callback failed when executed'],
|
||||
[new Error('callbackThree threw error on purpose')],
|
||||
]);
|
||||
});
|
||||
|
||||
it('should create package policy with last successful returned package policy', async () => {
|
||||
const request = getCreateKibanaRequest();
|
||||
await routeHandler(context, request, response);
|
||||
expect(response.ok).toHaveBeenCalled();
|
||||
expect(packagePolicyServiceMock.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',
|
||||
},
|
||||
four: {
|
||||
value: 'inserted by callbackFour',
|
||||
},
|
||||
},
|
||||
enabled: true,
|
||||
streams: [],
|
||||
type: 'endpoint',
|
||||
},
|
||||
],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
CreatePackagePolicyRequestSchema,
|
||||
UpdatePackagePolicyRequestSchema,
|
||||
DeletePackagePoliciesRequestSchema,
|
||||
NewPackagePolicy,
|
||||
} from '../../types';
|
||||
import { CreatePackagePolicyResponse, DeletePackagePoliciesResponse } from '../../../common';
|
||||
import { defaultIngestErrorHandler } from '../../errors';
|
||||
|
@ -77,31 +76,14 @@ export const createPackagePolicyHandler: RequestHandler<
|
|||
const soClient = context.core.savedObjects.client;
|
||||
const callCluster = context.core.elasticsearch.legacy.client.callAsCurrentUser;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
const logger = appContextService.getLogger();
|
||||
let newData = { ...request.body };
|
||||
try {
|
||||
// If we have external callbacks, then process those now before creating the actual package policy
|
||||
const externalCallbacks = appContextService.getExternalCallbacks('packagePolicyCreate');
|
||||
if (externalCallbacks && externalCallbacks.size > 0) {
|
||||
let updatedNewData: NewPackagePolicy = newData;
|
||||
|
||||
for (const callback of externalCallbacks) {
|
||||
try {
|
||||
// ensure that the returned value by the callback passes schema validation
|
||||
updatedNewData = CreatePackagePolicyRequestSchema.body.validate(
|
||||
await callback(updatedNewData, context, request)
|
||||
);
|
||||
} catch (error) {
|
||||
// Log the error, but keep going and process the other callbacks
|
||||
logger.error(
|
||||
'An external registered [packagePolicyCreate] callback failed when executed'
|
||||
);
|
||||
logger.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
newData = updatedNewData;
|
||||
}
|
||||
newData = await packagePolicyService.runExternalCallbacks(
|
||||
'packagePolicyCreate',
|
||||
newData,
|
||||
context,
|
||||
request
|
||||
);
|
||||
|
||||
// Create package policy
|
||||
const packagePolicy = await packagePolicyService.create(soClient, callCluster, newData, {
|
||||
|
@ -112,6 +94,12 @@ export const createPackagePolicyHandler: RequestHandler<
|
|||
body,
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.statusCode) {
|
||||
return response.customError({
|
||||
statusCode: error.statusCode,
|
||||
body: { message: error.message },
|
||||
});
|
||||
}
|
||||
return defaultIngestErrorHandler({ error, response });
|
||||
}
|
||||
};
|
||||
|
@ -123,16 +111,23 @@ export const updatePackagePolicyHandler: RequestHandler<
|
|||
> = async (context, request, response) => {
|
||||
const soClient = context.core.savedObjects.client;
|
||||
const user = (await appContextService.getSecurity()?.authc.getCurrentUser(request)) || undefined;
|
||||
const packagePolicy = await packagePolicyService.get(soClient, request.params.packagePolicyId);
|
||||
|
||||
if (!packagePolicy) {
|
||||
throw Boom.notFound('Package policy not found');
|
||||
}
|
||||
|
||||
let newData = { ...request.body };
|
||||
const pkg = newData.package || packagePolicy.package;
|
||||
const inputs = newData.inputs || packagePolicy.inputs;
|
||||
|
||||
try {
|
||||
const packagePolicy = await packagePolicyService.get(soClient, request.params.packagePolicyId);
|
||||
|
||||
if (!packagePolicy) {
|
||||
throw Boom.notFound('Package policy not found');
|
||||
}
|
||||
|
||||
const newData = { ...request.body };
|
||||
const pkg = newData.package || packagePolicy.package;
|
||||
const inputs = newData.inputs || packagePolicy.inputs;
|
||||
newData = await packagePolicyService.runExternalCallbacks(
|
||||
'packagePolicyUpdate',
|
||||
newData,
|
||||
context,
|
||||
request
|
||||
);
|
||||
|
||||
const updatedPackagePolicy = await packagePolicyService.update(
|
||||
soClient,
|
||||
|
|
|
@ -9,6 +9,12 @@ import { createPackagePolicyMock } from '../../common/mocks';
|
|||
import { packagePolicyService } from './package_policy';
|
||||
import { PackageInfo, PackagePolicySOAttributes } from '../types';
|
||||
import { SavedObjectsUpdateResponse } from 'src/core/server';
|
||||
import { httpServerMock } from 'src/core/server/mocks';
|
||||
import { KibanaRequest } from 'kibana/server';
|
||||
import { xpackMocks } from '../../../../mocks';
|
||||
import { ExternalCallback } from '..';
|
||||
import { appContextService } from './app_context';
|
||||
import { createAppContextStartContractMock } from '../mocks';
|
||||
|
||||
async function mockedGetAssetsData(_a: any, _b: any, dataset: string) {
|
||||
if (dataset === 'dataset1') {
|
||||
|
@ -318,4 +324,180 @@ describe('Package policy service', () => {
|
|||
).rejects.toThrow('Saved object [abc/123] conflict');
|
||||
});
|
||||
});
|
||||
|
||||
describe('runExternalCallbacks', () => {
|
||||
let context: ReturnType<typeof xpackMocks.createRequestHandlerContext>;
|
||||
let request: KibanaRequest;
|
||||
|
||||
const newPackagePolicy = {
|
||||
policy_id: 'a5ca00c0-b30c-11ea-9732-1bb05811278c',
|
||||
description: '',
|
||||
enabled: true,
|
||||
inputs: [],
|
||||
name: 'endpoint-1',
|
||||
namespace: 'default',
|
||||
output_id: '',
|
||||
package: {
|
||||
name: 'endpoint',
|
||||
title: 'Elastic Endpoint',
|
||||
version: '0.5.0',
|
||||
},
|
||||
};
|
||||
|
||||
const callbackCallingOrder: string[] = [];
|
||||
|
||||
// Callback one adds an input that includes a `config` property
|
||||
const callbackOne: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('one');
|
||||
return {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
type: 'endpoint',
|
||||
enabled: true,
|
||||
streams: [],
|
||||
config: {
|
||||
one: {
|
||||
value: 'inserted by callbackOne',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
// Callback two adds an additional `input[0].config` property
|
||||
const callbackTwo: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('two');
|
||||
return {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
...ds.inputs[0],
|
||||
config: {
|
||||
...ds.inputs[0].config,
|
||||
two: {
|
||||
value: 'inserted by callbackTwo',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
context = xpackMocks.createRequestHandlerContext();
|
||||
request = httpServerMock.createKibanaRequest();
|
||||
appContextService.start(createAppContextStartContractMock());
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
appContextService.stop();
|
||||
jest.clearAllMocks();
|
||||
callbackCallingOrder.length = 0;
|
||||
});
|
||||
|
||||
it('should call external callbacks in expected order', async () => {
|
||||
const callbackA: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('a');
|
||||
return ds;
|
||||
});
|
||||
|
||||
const callbackB: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('b');
|
||||
return ds;
|
||||
});
|
||||
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackA);
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackB);
|
||||
|
||||
await packagePolicyService.runExternalCallbacks(
|
||||
'packagePolicyCreate',
|
||||
newPackagePolicy,
|
||||
context,
|
||||
request
|
||||
);
|
||||
expect(callbackCallingOrder).toEqual(['a', 'b']);
|
||||
});
|
||||
|
||||
it('should feed package policy returned by last callback', async () => {
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackOne);
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackTwo);
|
||||
|
||||
await packagePolicyService.runExternalCallbacks(
|
||||
'packagePolicyCreate',
|
||||
newPackagePolicy,
|
||||
context,
|
||||
request
|
||||
);
|
||||
|
||||
expect((callbackOne as jest.Mock).mock.calls[0][0].inputs).toHaveLength(0);
|
||||
expect((callbackTwo as jest.Mock).mock.calls[0][0].inputs).toHaveLength(1);
|
||||
expect((callbackTwo as jest.Mock).mock.calls[0][0].inputs[0].config.one.value).toEqual(
|
||||
'inserted by callbackOne'
|
||||
);
|
||||
});
|
||||
|
||||
describe('with a callback that throws an exception', () => {
|
||||
const callbackThree: ExternalCallback[1] = jest.fn(async () => {
|
||||
callbackCallingOrder.push('three');
|
||||
throw new Error('callbackThree threw error on purpose');
|
||||
});
|
||||
|
||||
const callbackFour: ExternalCallback[1] = jest.fn(async (ds) => {
|
||||
callbackCallingOrder.push('four');
|
||||
return {
|
||||
...ds,
|
||||
inputs: [
|
||||
{
|
||||
...ds.inputs[0],
|
||||
config: {
|
||||
...ds.inputs[0].config,
|
||||
four: {
|
||||
value: 'inserted by callbackFour',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackOne);
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackTwo);
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackThree);
|
||||
appContextService.addExternalCallback('packagePolicyCreate', callbackFour);
|
||||
});
|
||||
|
||||
it('should fail to execute remaining callbacks after a callback exception', async () => {
|
||||
try {
|
||||
await packagePolicyService.runExternalCallbacks(
|
||||
'packagePolicyCreate',
|
||||
newPackagePolicy,
|
||||
context,
|
||||
request
|
||||
);
|
||||
} catch (e) {
|
||||
// expecting an error
|
||||
}
|
||||
|
||||
expect(callbackCallingOrder).toEqual(['one', 'two', 'three']);
|
||||
expect((callbackOne as jest.Mock).mock.calls.length).toBe(1);
|
||||
expect((callbackTwo as jest.Mock).mock.calls.length).toBe(1);
|
||||
expect((callbackThree as jest.Mock).mock.calls.length).toBe(1);
|
||||
expect((callbackFour as jest.Mock).mock.calls.length).toBe(0);
|
||||
});
|
||||
|
||||
it('should fail to return the package policy', async () => {
|
||||
expect(
|
||||
packagePolicyService.runExternalCallbacks(
|
||||
'packagePolicyCreate',
|
||||
newPackagePolicy,
|
||||
context,
|
||||
request
|
||||
)
|
||||
).rejects.toThrow('callbackThree threw error on purpose');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
* or more contributor license agreements. Licensed under the Elastic License;
|
||||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { SavedObjectsClientContract } from 'src/core/server';
|
||||
import { KibanaRequest, RequestHandlerContext, SavedObjectsClientContract } from 'src/core/server';
|
||||
import uuid from 'uuid';
|
||||
import { AuthenticatedUser } from '../../../security/server';
|
||||
import {
|
||||
|
@ -25,6 +25,8 @@ import {
|
|||
PackagePolicySOAttributes,
|
||||
RegistryPackage,
|
||||
CallESAsCurrentUser,
|
||||
NewPackagePolicySchema,
|
||||
UpdatePackagePolicySchema,
|
||||
} from '../types';
|
||||
import { agentPolicyService } from './agent_policy';
|
||||
import { outputService } from './output';
|
||||
|
@ -33,6 +35,8 @@ import { getPackageInfo, getInstallation, ensureInstalledPackage } from './epm/p
|
|||
import { getAssetsData } from './epm/packages/assets';
|
||||
import { compileTemplate } from './epm/agent/agent';
|
||||
import { normalizeKuery } from './saved_object';
|
||||
import { appContextService } from '.';
|
||||
import { ExternalCallback } from '..';
|
||||
|
||||
const SAVED_OBJECT_TYPE = PACKAGE_POLICY_SAVED_OBJECT_TYPE;
|
||||
|
||||
|
@ -391,6 +395,32 @@ class PackagePolicyService {
|
|||
|
||||
return Promise.all(inputsPromises);
|
||||
}
|
||||
|
||||
public async runExternalCallbacks(
|
||||
externalCallbackType: ExternalCallback[0],
|
||||
newPackagePolicy: NewPackagePolicy,
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
): Promise<NewPackagePolicy> {
|
||||
let newData = newPackagePolicy;
|
||||
|
||||
const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType);
|
||||
if (externalCallbacks && externalCallbacks.size > 0) {
|
||||
let updatedNewData: NewPackagePolicy = newData;
|
||||
|
||||
for (const callback of externalCallbacks) {
|
||||
const result = await callback(updatedNewData, context, request);
|
||||
if (externalCallbackType === 'packagePolicyCreate') {
|
||||
updatedNewData = NewPackagePolicySchema.validate(result);
|
||||
} else if (externalCallbackType === 'packagePolicyUpdate') {
|
||||
updatedNewData = UpdatePackagePolicySchema.validate(result);
|
||||
}
|
||||
}
|
||||
|
||||
newData = updatedNewData;
|
||||
}
|
||||
return newData;
|
||||
}
|
||||
}
|
||||
|
||||
function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyInput) {
|
||||
|
|
|
@ -18,7 +18,10 @@ import {
|
|||
PackagePolicyServiceInterface,
|
||||
} from '../../../fleet/server';
|
||||
import { PluginStartContract as AlertsPluginStartContract } from '../../../alerts/server';
|
||||
import { getPackagePolicyCreateCallback } from './ingest_integration';
|
||||
import {
|
||||
getPackagePolicyCreateCallback,
|
||||
getPackagePolicyUpdateCallback,
|
||||
} from './ingest_integration';
|
||||
import { ManifestManager } from './services/artifacts';
|
||||
import { MetadataQueryStrategy } from './types';
|
||||
import { MetadataQueryStrategyVersions } from '../../common/endpoint/types';
|
||||
|
@ -30,6 +33,7 @@ import { ElasticsearchAssetType } from '../../../fleet/common/types/models';
|
|||
import { metadataTransformPrefix } from '../../common/endpoint/constants';
|
||||
import { AppClientFactory } from '../client';
|
||||
import { ConfigType } from '../config';
|
||||
import { LicenseService } from '../../common/license/license';
|
||||
|
||||
export interface MetadataService {
|
||||
queryStrategy(
|
||||
|
@ -85,6 +89,7 @@ export type EndpointAppContextServiceStartContract = Partial<
|
|||
config: ConfigType;
|
||||
registerIngestCallback?: FleetStartContract['registerExternalCallback'];
|
||||
savedObjectsStart: SavedObjectsServiceStart;
|
||||
licenseService: LicenseService;
|
||||
};
|
||||
|
||||
/**
|
||||
|
@ -119,6 +124,11 @@ export class EndpointAppContextService {
|
|||
dependencies.alerts
|
||||
)
|
||||
);
|
||||
|
||||
dependencies.registerIngestCallback(
|
||||
'packagePolicyUpdate',
|
||||
getPackagePolicyUpdateCallback(dependencies.logger, dependencies.licenseService)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,22 +11,43 @@ import {
|
|||
getManifestManagerMock,
|
||||
ManifestManagerMockType,
|
||||
} from './services/artifacts/manifest_manager/manifest_manager.mock';
|
||||
import { getPackagePolicyCreateCallback } from './ingest_integration';
|
||||
import {
|
||||
getPackagePolicyCreateCallback,
|
||||
getPackagePolicyUpdateCallback,
|
||||
} from './ingest_integration';
|
||||
import { KibanaRequest, RequestHandlerContext } from 'kibana/server';
|
||||
import { createMockConfig, requestContextMock } from '../lib/detection_engine/routes/__mocks__';
|
||||
import { EndpointAppContextServiceStartContract } from './endpoint_app_context_services';
|
||||
import { createMockEndpointAppContextServiceStartContract } from './mocks';
|
||||
import { licenseMock } from '../../../licensing/common/licensing.mock';
|
||||
import { LicenseService } from '../../common/license/license';
|
||||
import { Subject } from 'rxjs';
|
||||
import { ILicense } from '../../../licensing/common/types';
|
||||
import { EndpointDocGenerator } from '../../common/endpoint/generate_data';
|
||||
import { ProtectionModes } from '../../common/endpoint/types';
|
||||
|
||||
describe('ingest_integration tests ', () => {
|
||||
let endpointAppContextMock: EndpointAppContextServiceStartContract;
|
||||
let req: KibanaRequest;
|
||||
let ctx: RequestHandlerContext;
|
||||
const maxTimelineImportExportSize = createMockConfig().maxTimelineImportExportSize;
|
||||
let licenseEmitter: Subject<ILicense>;
|
||||
let licenseService: LicenseService;
|
||||
const Platinum = licenseMock.createLicense({ license: { type: 'platinum', mode: 'platinum' } });
|
||||
const Gold = licenseMock.createLicense({ license: { type: 'gold', mode: 'gold' } });
|
||||
const generator = new EndpointDocGenerator();
|
||||
|
||||
beforeEach(() => {
|
||||
endpointAppContextMock = createMockEndpointAppContextServiceStartContract();
|
||||
ctx = requestContextMock.createTools().context;
|
||||
req = httpServerMock.createKibanaRequest();
|
||||
licenseEmitter = new Subject();
|
||||
licenseService = new LicenseService();
|
||||
licenseService.start(licenseEmitter);
|
||||
});
|
||||
afterEach(() => {
|
||||
licenseService.stop();
|
||||
licenseEmitter.complete();
|
||||
});
|
||||
|
||||
describe('ingest_integration sanity checks', () => {
|
||||
|
@ -179,4 +200,45 @@ describe('ingest_integration tests ', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
describe('when the license is below platinum', () => {
|
||||
beforeEach(() => {
|
||||
licenseEmitter.next(Gold); // set license level to gold
|
||||
});
|
||||
it('returns an error if paid features are turned on in the policy', async () => {
|
||||
const mockPolicy = policyConfigFactory();
|
||||
mockPolicy.windows.popup.malware.message = 'paid feature';
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
const callback = getPackagePolicyUpdateCallback(logger, licenseService);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
|
||||
await expect(() => callback(policyConfig, ctx, req)).rejects.toThrow(
|
||||
'Requires Platinum license'
|
||||
);
|
||||
});
|
||||
it('updates successfully if no paid features are turned on in the policy', async () => {
|
||||
const mockPolicy = policyConfigFactory();
|
||||
mockPolicy.windows.malware.mode = ProtectionModes.detect;
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
const callback = getPackagePolicyUpdateCallback(logger, licenseService);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
|
||||
const updatedPolicyConfig = await callback(policyConfig, ctx, req);
|
||||
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy);
|
||||
});
|
||||
});
|
||||
describe('when the license is at least platinum', () => {
|
||||
beforeEach(() => {
|
||||
licenseEmitter.next(Platinum); // set license level to platinum
|
||||
});
|
||||
it('updates successfully when paid features are turned on', async () => {
|
||||
const mockPolicy = policyConfigFactory();
|
||||
mockPolicy.windows.popup.malware.message = 'paid feature';
|
||||
const logger = loggingSystemMock.create().get('ingest_integration.test');
|
||||
const callback = getPackagePolicyUpdateCallback(logger, licenseService);
|
||||
const policyConfig = generator.generatePolicyPackagePolicy();
|
||||
policyConfig.inputs[0]!.config!.policy.value = mockPolicy;
|
||||
const updatedPolicyConfig = await callback(policyConfig, ctx, req);
|
||||
expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,7 +8,7 @@ import { PluginStartContract as AlertsStartContract } from '../../../alerts/serv
|
|||
import { SecurityPluginSetup } from '../../../security/server';
|
||||
import { ExternalCallback } from '../../../fleet/server';
|
||||
import { KibanaRequest, Logger, RequestHandlerContext } from '../../../../../src/core/server';
|
||||
import { NewPackagePolicy } from '../../../fleet/common/types/models';
|
||||
import { NewPackagePolicy, UpdatePackagePolicy } from '../../../fleet/common/types/models';
|
||||
import { factory as policyConfigFactory } from '../../common/endpoint/models/policy_config';
|
||||
import { NewPolicyData } from '../../common/endpoint/types';
|
||||
import { ManifestManager } from './services/artifacts';
|
||||
|
@ -20,6 +20,8 @@ import { AppClientFactory } from '../client';
|
|||
import { createDetectionIndex } from '../lib/detection_engine/routes/index/create_index_route';
|
||||
import { createPrepackagedRules } from '../lib/detection_engine/routes/rules/add_prepackaged_rules_route';
|
||||
import { buildFrameworkRequest } from '../lib/timeline/routes/utils/common';
|
||||
import { isEndpointPolicyValidForLicense } from '../../common/license/policy_config';
|
||||
import { LicenseService } from '../../common/license/license';
|
||||
|
||||
const getManifest = async (logger: Logger, manifestManager: ManifestManager): Promise<Manifest> => {
|
||||
let manifest: Manifest | null = null;
|
||||
|
@ -164,3 +166,32 @@ export const getPackagePolicyCreateCallback = (
|
|||
|
||||
return handlePackagePolicyCreate;
|
||||
};
|
||||
|
||||
export const getPackagePolicyUpdateCallback = (
|
||||
logger: Logger,
|
||||
licenseService: LicenseService
|
||||
): ExternalCallback[1] => {
|
||||
const handlePackagePolicyUpdate = async (
|
||||
newPackagePolicy: NewPackagePolicy,
|
||||
context: RequestHandlerContext,
|
||||
request: KibanaRequest
|
||||
): Promise<UpdatePackagePolicy> => {
|
||||
if (newPackagePolicy.package?.name !== 'endpoint') {
|
||||
return newPackagePolicy;
|
||||
}
|
||||
|
||||
if (
|
||||
!isEndpointPolicyValidForLicense(
|
||||
newPackagePolicy.inputs[0].config?.policy?.value,
|
||||
licenseService.getLicenseInformation()
|
||||
)
|
||||
) {
|
||||
logger.warn('Incorrect license tier for paid policy fields');
|
||||
const licenseError: Error & { statusCode?: number } = new Error('Requires Platinum license');
|
||||
licenseError.statusCode = 403;
|
||||
throw licenseError;
|
||||
}
|
||||
return newPackagePolicy;
|
||||
};
|
||||
return handlePackagePolicyUpdate;
|
||||
};
|
||||
|
|
|
@ -25,6 +25,8 @@ import { ManifestManager } from './services/artifacts/manifest_manager/manifest_
|
|||
import { getManifestManagerMock } from './services/artifacts/manifest_manager/manifest_manager.mock';
|
||||
import { EndpointAppContext } from './types';
|
||||
import { MetadataRequestContext } from './routes/metadata/handlers';
|
||||
// import { licenseMock } from '../../../licensing/common/licensing.mock';
|
||||
import { LicenseService } from '../../common/license/license';
|
||||
|
||||
/**
|
||||
* Creates a mocked EndpointAppContext.
|
||||
|
@ -72,6 +74,7 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
|
|||
security: securityMock.createSetup(),
|
||||
alerts: alertsMock.createStart(),
|
||||
config,
|
||||
licenseService: new LicenseService(),
|
||||
registerIngestCallback: jest.fn<
|
||||
ReturnType<FleetStartContract['registerExternalCallback']>,
|
||||
Parameters<FleetStartContract['registerExternalCallback']>
|
||||
|
|
|
@ -359,6 +359,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
|||
manifestManager,
|
||||
registerIngestCallback,
|
||||
savedObjectsStart: core.savedObjects,
|
||||
licenseService,
|
||||
});
|
||||
|
||||
if (exceptionListsStartEnabled() && this.manifestTask) {
|
||||
|
|
Loading…
Reference in a new issue