[Security Solution][Endpoint Exceptions] - Fix bug where endpoint exceptions list not created when expected (#88232)

## Summary

Addresses issue issue 87110

**Issue**
When prepackaged rules were created during the Endpoint Security enrollment flow, the endpoint exceptions list was failing to be created. As a result, when a user navigated to the `Endpoint Security` rule to add an exception it would display errors and not allow a user to add exceptions.
This commit is contained in:
Yara Tercero 2021-01-19 17:36:05 -05:00 committed by GitHub
parent 1db32352b2
commit 5f53b649c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 104 additions and 10 deletions

View file

@ -28,6 +28,7 @@ export class ExceptionListClientMock extends ExceptionListClient {
public findExceptionListItem = jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock());
public findExceptionList = jest.fn().mockResolvedValue(getFoundExceptionListSchemaMock());
public createTrustedAppsList = jest.fn().mockResolvedValue(getTrustedAppsListSchemaMock());
public createEndpointList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
}
export const getExceptionListClientMock = (): ExceptionListClient => {

View file

@ -9,6 +9,7 @@ import {
SavedObjectsServiceStart,
SavedObjectsClientContract,
} from 'src/core/server';
import { ExceptionListClient } from '../../../lists/server';
import { SecurityPluginSetup } from '../../../security/server';
import {
AgentService,
@ -90,6 +91,7 @@ export type EndpointAppContextServiceStartContract = Partial<
registerIngestCallback?: FleetStartContract['registerExternalCallback'];
savedObjectsStart: SavedObjectsServiceStart;
licenseService: LicenseService;
exceptionListsClient: ExceptionListClient | undefined;
};
/**
@ -121,7 +123,8 @@ export class EndpointAppContextService {
dependencies.appClientFactory,
dependencies.config.maxTimelineImportExportSize,
dependencies.security,
dependencies.alerts
dependencies.alerts,
dependencies.exceptionListsClient
)
);

View file

@ -25,11 +25,14 @@ import { Subject } from 'rxjs';
import { ILicense } from '../../../licensing/common/types';
import { EndpointDocGenerator } from '../../common/endpoint/generate_data';
import { ProtectionModes } from '../../common/endpoint/types';
import { getExceptionListClientMock } from '../../../lists/server/services/exception_lists/exception_list_client.mock';
import { ExceptionListClient } from '../../../lists/server';
describe('ingest_integration tests ', () => {
let endpointAppContextMock: EndpointAppContextServiceStartContract;
let req: KibanaRequest;
let ctx: RequestHandlerContext;
const exceptionListClient: ExceptionListClient = getExceptionListClientMock();
const maxTimelineImportExportSize = createMockConfig().maxTimelineImportExportSize;
let licenseEmitter: Subject<ILicense>;
let licenseService: LicenseService;
@ -63,7 +66,8 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.appClientFactory,
maxTimelineImportExportSize,
endpointAppContextMock.security,
endpointAppContextMock.alerts
endpointAppContextMock.alerts,
exceptionListClient
);
const policyConfig = createNewPackagePolicyMock(); // policy config without manifest
const newPolicyConfig = await callback(policyConfig, ctx, req); // policy config WITH manifest
@ -140,7 +144,8 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.appClientFactory,
maxTimelineImportExportSize,
endpointAppContextMock.security,
endpointAppContextMock.alerts
endpointAppContextMock.alerts,
exceptionListClient
);
const policyConfig = createNewPackagePolicyMock();
const newPolicyConfig = await callback(policyConfig, ctx, req);
@ -167,7 +172,8 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.appClientFactory,
maxTimelineImportExportSize,
endpointAppContextMock.security,
endpointAppContextMock.alerts
endpointAppContextMock.alerts,
exceptionListClient
);
const policyConfig = createNewPackagePolicyMock();
const newPolicyConfig = await callback(policyConfig, ctx, req);
@ -188,7 +194,8 @@ describe('ingest_integration tests ', () => {
endpointAppContextMock.appClientFactory,
maxTimelineImportExportSize,
endpointAppContextMock.security,
endpointAppContextMock.alerts
endpointAppContextMock.alerts,
exceptionListClient
);
const policyConfig = createNewPackagePolicyMock();
const newPolicyConfig = await callback(policyConfig, ctx, req);
@ -199,6 +206,32 @@ describe('ingest_integration tests ', () => {
lastComputed!.toEndpointFormat()
);
});
test('policy creation succeeds even if endpoint exception list creation fails', async () => {
const mockError = new Error('error creating endpoint list');
const logger = loggingSystemMock.create().get('ingest_integration.test');
const manifestManager = getManifestManagerMock();
const lastComputed = await manifestManager.getLastComputedManifest();
exceptionListClient.createEndpointList = jest.fn().mockRejectedValue(mockError);
const callback = getPackagePolicyCreateCallback(
logger,
manifestManager,
endpointAppContextMock.appClientFactory,
maxTimelineImportExportSize,
endpointAppContextMock.security,
endpointAppContextMock.alerts,
exceptionListClient
);
const policyConfig = createNewPackagePolicyMock();
const newPolicyConfig = await callback(policyConfig, ctx, req);
expect(exceptionListClient.createEndpointList).toHaveBeenCalled();
expect(newPolicyConfig.inputs[0]!.type).toEqual('endpoint');
expect(newPolicyConfig.inputs[0]!.config!.policy.value).toEqual(policyConfigFactory());
expect(newPolicyConfig.inputs[0]!.config!.artifact_manifest.value).toEqual(
lastComputed!.toEndpointFormat()
);
});
});
describe('when the license is below platinum', () => {
beforeEach(() => {

View file

@ -4,6 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/
import { ExceptionListClient } from '../../../lists/server';
import { PluginStartContract as AlertsStartContract } from '../../../alerts/server';
import { SecurityPluginSetup } from '../../../security/server';
import { ExternalCallback } from '../../../fleet/server';
@ -84,7 +85,8 @@ export const getPackagePolicyCreateCallback = (
appClientFactory: AppClientFactory,
maxTimelineImportExportSize: number,
securitySetup: SecurityPluginSetup,
alerts: AlertsStartContract
alerts: AlertsStartContract,
exceptionsClient: ExceptionListClient | undefined
): ExternalCallback[1] => {
const handlePackagePolicyCreate = async (
newPackagePolicy: NewPackagePolicy,
@ -119,7 +121,8 @@ export const getPackagePolicyCreateCallback = (
appClient,
alerts.getAlertsClientWithRequest(request),
frameworkRequest,
maxTimelineImportExportSize
maxTimelineImportExportSize,
exceptionsClient
);
} catch (err) {
logger.error(

View file

@ -6,6 +6,7 @@
import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
import { listMock } from '../../../lists/server/mocks';
import { securityMock } from '../../../security/server/mocks';
import { alertsMock } from '../../../alerts/server/mocks';
import { xpackMocks } from '../../../../mocks';
@ -79,6 +80,7 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
ReturnType<FleetStartContract['registerExternalCallback']>,
Parameters<FleetStartContract['registerExternalCallback']>
>(),
exceptionListsClient: listMock.getExceptionListClient(),
};
};

View file

@ -15,7 +15,11 @@ import { requestContextMock, serverMock, createMockConfig, mockGetCurrentUser }
import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema';
import { SecurityPluginSetup } from '../../../../../../security/server';
import { installPrepackagedTimelines } from '../../../timeline/routes/utils/install_prepacked_timelines';
import { addPrepackedRulesRoute } from './add_prepackaged_rules_route';
import { addPrepackedRulesRoute, createPrepackagedRules } from './add_prepackaged_rules_route';
import { listMock } from '../../../../../../lists/server/mocks';
import { siemMock } from '../../../../mocks';
import { FrameworkRequest } from '../../../framework';
import { ExceptionListClient } from '../../../../../../lists/server';
jest.mock('../../rules/get_prepackaged_rules', () => {
return {
@ -66,9 +70,11 @@ jest.mock('../../../timeline/routes/utils/install_prepacked_timelines', () => {
});
describe('add_prepackaged_rules_route', () => {
const siemMockClient = siemMock.createClient();
let server: ReturnType<typeof serverMock.create>;
let { clients, context } = requestContextMock.createTools();
let securitySetup: SecurityPluginSetup;
let mockExceptionsClient: ExceptionListClient;
beforeEach(() => {
server = serverMock.create();
@ -80,6 +86,8 @@ describe('add_prepackaged_rules_route', () => {
authz: {},
} as unknown) as SecurityPluginSetup;
mockExceptionsClient = listMock.getExceptionListClient();
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getNonEmptyIndex());
clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
@ -271,4 +279,40 @@ describe('add_prepackaged_rules_route', () => {
timelines_updated: 0,
});
});
describe('createPrepackagedRules', () => {
test('uses exception lists client from context when available', async () => {
context.lists = {
getExceptionListClient: jest.fn(),
getListClient: jest.fn(),
};
await createPrepackagedRules(
context,
siemMockClient,
clients.alertsClient,
{} as FrameworkRequest,
1200,
mockExceptionsClient
);
expect(mockExceptionsClient.createEndpointList).not.toHaveBeenCalled();
expect(context.lists?.getExceptionListClient).toHaveBeenCalled();
});
test('uses passed in exceptions list client when lists client not available in context', async () => {
const { lists, ...myContext } = context;
await createPrepackagedRules(
myContext,
siemMockClient,
clients.alertsClient,
{} as FrameworkRequest,
1200,
mockExceptionsClient
);
expect(mockExceptionsClient.createEndpointList).toHaveBeenCalled();
});
});
});

View file

@ -32,6 +32,8 @@ import { transformError, buildSiemResponse } from '../utils';
import { AlertsClient } from '../../../../../../alerts/server';
import { FrameworkRequest } from '../../../framework';
import { ExceptionListClient } from '../../../../../../lists/server';
export const addPrepackedRulesRoute = (
router: IRouter,
config: ConfigType,
@ -89,17 +91,22 @@ export const createPrepackagedRules = async (
siemClient: AppClient,
alertsClient: AlertsClient,
frameworkRequest: FrameworkRequest,
maxTimelineImportExportSize: number
maxTimelineImportExportSize: number,
exceptionsClient?: ExceptionListClient
): Promise<PrePackagedRulesAndTimelinesSchema | null> => {
const clusterClient = context.core.elasticsearch.legacy.client;
const savedObjectsClient = context.core.savedObjects.client;
const exceptionsListClient =
context.lists != null ? context.lists.getExceptionListClient() : exceptionsClient;
if (!siemClient || !alertsClient) {
throw new PrepackagedRulesError('', 404);
}
// This will create the endpoint list if it does not exist yet
await context.lists?.getExceptionListClient().createEndpointList();
if (exceptionsListClient != null) {
await exceptionsListClient.createEndpointList();
}
const rulesFromFileSystem = getPrepackagedRules();
const prepackagedRules = await getExistingPrepackagedRules({ alertsClient });

View file

@ -374,6 +374,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
registerIngestCallback,
savedObjectsStart: core.savedObjects,
licenseService,
exceptionListsClient: this.lists!.getExceptionListClient(savedObjectsClient, 'kibana'),
});
this.telemetryEventsSender.start(core, plugins.telemetry, plugins.taskManager);