[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:
parent
1db32352b2
commit
5f53b649c6
|
@ -28,6 +28,7 @@ export class ExceptionListClientMock extends ExceptionListClient {
|
||||||
public findExceptionListItem = jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock());
|
public findExceptionListItem = jest.fn().mockResolvedValue(getFoundExceptionListItemSchemaMock());
|
||||||
public findExceptionList = jest.fn().mockResolvedValue(getFoundExceptionListSchemaMock());
|
public findExceptionList = jest.fn().mockResolvedValue(getFoundExceptionListSchemaMock());
|
||||||
public createTrustedAppsList = jest.fn().mockResolvedValue(getTrustedAppsListSchemaMock());
|
public createTrustedAppsList = jest.fn().mockResolvedValue(getTrustedAppsListSchemaMock());
|
||||||
|
public createEndpointList = jest.fn().mockResolvedValue(getExceptionListSchemaMock());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getExceptionListClientMock = (): ExceptionListClient => {
|
export const getExceptionListClientMock = (): ExceptionListClient => {
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {
|
||||||
SavedObjectsServiceStart,
|
SavedObjectsServiceStart,
|
||||||
SavedObjectsClientContract,
|
SavedObjectsClientContract,
|
||||||
} from 'src/core/server';
|
} from 'src/core/server';
|
||||||
|
import { ExceptionListClient } from '../../../lists/server';
|
||||||
import { SecurityPluginSetup } from '../../../security/server';
|
import { SecurityPluginSetup } from '../../../security/server';
|
||||||
import {
|
import {
|
||||||
AgentService,
|
AgentService,
|
||||||
|
@ -90,6 +91,7 @@ export type EndpointAppContextServiceStartContract = Partial<
|
||||||
registerIngestCallback?: FleetStartContract['registerExternalCallback'];
|
registerIngestCallback?: FleetStartContract['registerExternalCallback'];
|
||||||
savedObjectsStart: SavedObjectsServiceStart;
|
savedObjectsStart: SavedObjectsServiceStart;
|
||||||
licenseService: LicenseService;
|
licenseService: LicenseService;
|
||||||
|
exceptionListsClient: ExceptionListClient | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -121,7 +123,8 @@ export class EndpointAppContextService {
|
||||||
dependencies.appClientFactory,
|
dependencies.appClientFactory,
|
||||||
dependencies.config.maxTimelineImportExportSize,
|
dependencies.config.maxTimelineImportExportSize,
|
||||||
dependencies.security,
|
dependencies.security,
|
||||||
dependencies.alerts
|
dependencies.alerts,
|
||||||
|
dependencies.exceptionListsClient
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -25,11 +25,14 @@ import { Subject } from 'rxjs';
|
||||||
import { ILicense } from '../../../licensing/common/types';
|
import { ILicense } from '../../../licensing/common/types';
|
||||||
import { EndpointDocGenerator } from '../../common/endpoint/generate_data';
|
import { EndpointDocGenerator } from '../../common/endpoint/generate_data';
|
||||||
import { ProtectionModes } from '../../common/endpoint/types';
|
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 ', () => {
|
describe('ingest_integration tests ', () => {
|
||||||
let endpointAppContextMock: EndpointAppContextServiceStartContract;
|
let endpointAppContextMock: EndpointAppContextServiceStartContract;
|
||||||
let req: KibanaRequest;
|
let req: KibanaRequest;
|
||||||
let ctx: RequestHandlerContext;
|
let ctx: RequestHandlerContext;
|
||||||
|
const exceptionListClient: ExceptionListClient = getExceptionListClientMock();
|
||||||
const maxTimelineImportExportSize = createMockConfig().maxTimelineImportExportSize;
|
const maxTimelineImportExportSize = createMockConfig().maxTimelineImportExportSize;
|
||||||
let licenseEmitter: Subject<ILicense>;
|
let licenseEmitter: Subject<ILicense>;
|
||||||
let licenseService: LicenseService;
|
let licenseService: LicenseService;
|
||||||
|
@ -63,7 +66,8 @@ describe('ingest_integration tests ', () => {
|
||||||
endpointAppContextMock.appClientFactory,
|
endpointAppContextMock.appClientFactory,
|
||||||
maxTimelineImportExportSize,
|
maxTimelineImportExportSize,
|
||||||
endpointAppContextMock.security,
|
endpointAppContextMock.security,
|
||||||
endpointAppContextMock.alerts
|
endpointAppContextMock.alerts,
|
||||||
|
exceptionListClient
|
||||||
);
|
);
|
||||||
const policyConfig = createNewPackagePolicyMock(); // policy config without manifest
|
const policyConfig = createNewPackagePolicyMock(); // policy config without manifest
|
||||||
const newPolicyConfig = await callback(policyConfig, ctx, req); // policy config WITH manifest
|
const newPolicyConfig = await callback(policyConfig, ctx, req); // policy config WITH manifest
|
||||||
|
@ -140,7 +144,8 @@ describe('ingest_integration tests ', () => {
|
||||||
endpointAppContextMock.appClientFactory,
|
endpointAppContextMock.appClientFactory,
|
||||||
maxTimelineImportExportSize,
|
maxTimelineImportExportSize,
|
||||||
endpointAppContextMock.security,
|
endpointAppContextMock.security,
|
||||||
endpointAppContextMock.alerts
|
endpointAppContextMock.alerts,
|
||||||
|
exceptionListClient
|
||||||
);
|
);
|
||||||
const policyConfig = createNewPackagePolicyMock();
|
const policyConfig = createNewPackagePolicyMock();
|
||||||
const newPolicyConfig = await callback(policyConfig, ctx, req);
|
const newPolicyConfig = await callback(policyConfig, ctx, req);
|
||||||
|
@ -167,7 +172,8 @@ describe('ingest_integration tests ', () => {
|
||||||
endpointAppContextMock.appClientFactory,
|
endpointAppContextMock.appClientFactory,
|
||||||
maxTimelineImportExportSize,
|
maxTimelineImportExportSize,
|
||||||
endpointAppContextMock.security,
|
endpointAppContextMock.security,
|
||||||
endpointAppContextMock.alerts
|
endpointAppContextMock.alerts,
|
||||||
|
exceptionListClient
|
||||||
);
|
);
|
||||||
const policyConfig = createNewPackagePolicyMock();
|
const policyConfig = createNewPackagePolicyMock();
|
||||||
const newPolicyConfig = await callback(policyConfig, ctx, req);
|
const newPolicyConfig = await callback(policyConfig, ctx, req);
|
||||||
|
@ -188,7 +194,8 @@ describe('ingest_integration tests ', () => {
|
||||||
endpointAppContextMock.appClientFactory,
|
endpointAppContextMock.appClientFactory,
|
||||||
maxTimelineImportExportSize,
|
maxTimelineImportExportSize,
|
||||||
endpointAppContextMock.security,
|
endpointAppContextMock.security,
|
||||||
endpointAppContextMock.alerts
|
endpointAppContextMock.alerts,
|
||||||
|
exceptionListClient
|
||||||
);
|
);
|
||||||
const policyConfig = createNewPackagePolicyMock();
|
const policyConfig = createNewPackagePolicyMock();
|
||||||
const newPolicyConfig = await callback(policyConfig, ctx, req);
|
const newPolicyConfig = await callback(policyConfig, ctx, req);
|
||||||
|
@ -199,6 +206,32 @@ describe('ingest_integration tests ', () => {
|
||||||
lastComputed!.toEndpointFormat()
|
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', () => {
|
describe('when the license is below platinum', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
* you may not use this file except in compliance with the Elastic License.
|
* 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 { PluginStartContract as AlertsStartContract } from '../../../alerts/server';
|
||||||
import { SecurityPluginSetup } from '../../../security/server';
|
import { SecurityPluginSetup } from '../../../security/server';
|
||||||
import { ExternalCallback } from '../../../fleet/server';
|
import { ExternalCallback } from '../../../fleet/server';
|
||||||
|
@ -84,7 +85,8 @@ export const getPackagePolicyCreateCallback = (
|
||||||
appClientFactory: AppClientFactory,
|
appClientFactory: AppClientFactory,
|
||||||
maxTimelineImportExportSize: number,
|
maxTimelineImportExportSize: number,
|
||||||
securitySetup: SecurityPluginSetup,
|
securitySetup: SecurityPluginSetup,
|
||||||
alerts: AlertsStartContract
|
alerts: AlertsStartContract,
|
||||||
|
exceptionsClient: ExceptionListClient | undefined
|
||||||
): ExternalCallback[1] => {
|
): ExternalCallback[1] => {
|
||||||
const handlePackagePolicyCreate = async (
|
const handlePackagePolicyCreate = async (
|
||||||
newPackagePolicy: NewPackagePolicy,
|
newPackagePolicy: NewPackagePolicy,
|
||||||
|
@ -119,7 +121,8 @@ export const getPackagePolicyCreateCallback = (
|
||||||
appClient,
|
appClient,
|
||||||
alerts.getAlertsClientWithRequest(request),
|
alerts.getAlertsClientWithRequest(request),
|
||||||
frameworkRequest,
|
frameworkRequest,
|
||||||
maxTimelineImportExportSize
|
maxTimelineImportExportSize,
|
||||||
|
exceptionsClient
|
||||||
);
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
|
import { ILegacyScopedClusterClient, SavedObjectsClientContract } from 'kibana/server';
|
||||||
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
|
import { loggingSystemMock, savedObjectsServiceMock } from 'src/core/server/mocks';
|
||||||
|
import { listMock } from '../../../lists/server/mocks';
|
||||||
import { securityMock } from '../../../security/server/mocks';
|
import { securityMock } from '../../../security/server/mocks';
|
||||||
import { alertsMock } from '../../../alerts/server/mocks';
|
import { alertsMock } from '../../../alerts/server/mocks';
|
||||||
import { xpackMocks } from '../../../../mocks';
|
import { xpackMocks } from '../../../../mocks';
|
||||||
|
@ -79,6 +80,7 @@ export const createMockEndpointAppContextServiceStartContract = (): jest.Mocked<
|
||||||
ReturnType<FleetStartContract['registerExternalCallback']>,
|
ReturnType<FleetStartContract['registerExternalCallback']>,
|
||||||
Parameters<FleetStartContract['registerExternalCallback']>
|
Parameters<FleetStartContract['registerExternalCallback']>
|
||||||
>(),
|
>(),
|
||||||
|
exceptionListsClient: listMock.getExceptionListClient(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,11 @@ import { requestContextMock, serverMock, createMockConfig, mockGetCurrentUser }
|
||||||
import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema';
|
import { AddPrepackagedRulesSchemaDecoded } from '../../../../../common/detection_engine/schemas/request/add_prepackaged_rules_schema';
|
||||||
import { SecurityPluginSetup } from '../../../../../../security/server';
|
import { SecurityPluginSetup } from '../../../../../../security/server';
|
||||||
import { installPrepackagedTimelines } from '../../../timeline/routes/utils/install_prepacked_timelines';
|
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', () => {
|
jest.mock('../../rules/get_prepackaged_rules', () => {
|
||||||
return {
|
return {
|
||||||
|
@ -66,9 +70,11 @@ jest.mock('../../../timeline/routes/utils/install_prepacked_timelines', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('add_prepackaged_rules_route', () => {
|
describe('add_prepackaged_rules_route', () => {
|
||||||
|
const siemMockClient = siemMock.createClient();
|
||||||
let server: ReturnType<typeof serverMock.create>;
|
let server: ReturnType<typeof serverMock.create>;
|
||||||
let { clients, context } = requestContextMock.createTools();
|
let { clients, context } = requestContextMock.createTools();
|
||||||
let securitySetup: SecurityPluginSetup;
|
let securitySetup: SecurityPluginSetup;
|
||||||
|
let mockExceptionsClient: ExceptionListClient;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
server = serverMock.create();
|
server = serverMock.create();
|
||||||
|
@ -80,6 +86,8 @@ describe('add_prepackaged_rules_route', () => {
|
||||||
authz: {},
|
authz: {},
|
||||||
} as unknown) as SecurityPluginSetup;
|
} as unknown) as SecurityPluginSetup;
|
||||||
|
|
||||||
|
mockExceptionsClient = listMock.getExceptionListClient();
|
||||||
|
|
||||||
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getNonEmptyIndex());
|
clients.clusterClient.callAsCurrentUser.mockResolvedValue(getNonEmptyIndex());
|
||||||
clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
|
clients.alertsClient.find.mockResolvedValue(getFindResultWithSingleHit());
|
||||||
|
|
||||||
|
@ -271,4 +279,40 @@ describe('add_prepackaged_rules_route', () => {
|
||||||
timelines_updated: 0,
|
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();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -32,6 +32,8 @@ import { transformError, buildSiemResponse } from '../utils';
|
||||||
import { AlertsClient } from '../../../../../../alerts/server';
|
import { AlertsClient } from '../../../../../../alerts/server';
|
||||||
import { FrameworkRequest } from '../../../framework';
|
import { FrameworkRequest } from '../../../framework';
|
||||||
|
|
||||||
|
import { ExceptionListClient } from '../../../../../../lists/server';
|
||||||
|
|
||||||
export const addPrepackedRulesRoute = (
|
export const addPrepackedRulesRoute = (
|
||||||
router: IRouter,
|
router: IRouter,
|
||||||
config: ConfigType,
|
config: ConfigType,
|
||||||
|
@ -89,17 +91,22 @@ export const createPrepackagedRules = async (
|
||||||
siemClient: AppClient,
|
siemClient: AppClient,
|
||||||
alertsClient: AlertsClient,
|
alertsClient: AlertsClient,
|
||||||
frameworkRequest: FrameworkRequest,
|
frameworkRequest: FrameworkRequest,
|
||||||
maxTimelineImportExportSize: number
|
maxTimelineImportExportSize: number,
|
||||||
|
exceptionsClient?: ExceptionListClient
|
||||||
): Promise<PrePackagedRulesAndTimelinesSchema | null> => {
|
): Promise<PrePackagedRulesAndTimelinesSchema | null> => {
|
||||||
const clusterClient = context.core.elasticsearch.legacy.client;
|
const clusterClient = context.core.elasticsearch.legacy.client;
|
||||||
const savedObjectsClient = context.core.savedObjects.client;
|
const savedObjectsClient = context.core.savedObjects.client;
|
||||||
|
const exceptionsListClient =
|
||||||
|
context.lists != null ? context.lists.getExceptionListClient() : exceptionsClient;
|
||||||
|
|
||||||
if (!siemClient || !alertsClient) {
|
if (!siemClient || !alertsClient) {
|
||||||
throw new PrepackagedRulesError('', 404);
|
throw new PrepackagedRulesError('', 404);
|
||||||
}
|
}
|
||||||
|
|
||||||
// This will create the endpoint list if it does not exist yet
|
// 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 rulesFromFileSystem = getPrepackagedRules();
|
||||||
const prepackagedRules = await getExistingPrepackagedRules({ alertsClient });
|
const prepackagedRules = await getExistingPrepackagedRules({ alertsClient });
|
||||||
|
|
|
@ -374,6 +374,7 @@ export class Plugin implements IPlugin<PluginSetup, PluginStart, SetupPlugins, S
|
||||||
registerIngestCallback,
|
registerIngestCallback,
|
||||||
savedObjectsStart: core.savedObjects,
|
savedObjectsStart: core.savedObjects,
|
||||||
licenseService,
|
licenseService,
|
||||||
|
exceptionListsClient: this.lists!.getExceptionListClient(savedObjectsClient, 'kibana'),
|
||||||
});
|
});
|
||||||
|
|
||||||
this.telemetryEventsSender.start(core, plugins.telemetry, plugins.taskManager);
|
this.telemetryEventsSender.start(core, plugins.telemetry, plugins.taskManager);
|
||||||
|
|
Loading…
Reference in a new issue