mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 01:13:23 -04:00
[Security Solution] [Action Connectors] Fix exporting predefined connectors (#153687)
## Summary - Addresses https://github.com/elastic/kibana/issues/153619 **Reason:** - As predefined connectors are not Saved objects, the export method was failing to get their exported objects. **Solution:** - Filter out `Predefined Action` ids from the user's action ids because we don't need to export them as they are already in the user env, and they won't be removed or changed **References** https://www.elastic.co/guide/en/kibana/8.7/pre-configured-connectors.html --------- Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
parent
4d24951a6a
commit
e52eb267b7
9 changed files with 477 additions and 16 deletions
|
@ -332,6 +332,7 @@ export const performBulkActionRoute = (
|
|||
const rulesClient = ctx.alerting.getRulesClient();
|
||||
const exceptionsClient = ctx.lists?.getExceptionListClient();
|
||||
const savedObjectsClient = ctx.core.savedObjects.client;
|
||||
const actionsClient = (await ctx.actions)?.getActionsClient();
|
||||
|
||||
const { getExporter, getClient } = (await ctx.core).savedObjects;
|
||||
const client = getClient({ includedHiddenTypes: ['action'] });
|
||||
|
@ -565,7 +566,8 @@ export const performBulkActionRoute = (
|
|||
rules.map(({ params }) => ({ rule_id: params.ruleId })),
|
||||
logger,
|
||||
exporter,
|
||||
request
|
||||
request,
|
||||
actionsClient
|
||||
);
|
||||
|
||||
const responseBody = `${exported.rulesNdjson}${exported.exceptionLists}${exported.actionConnectors}${exported.exportDetails}`;
|
||||
|
|
|
@ -45,6 +45,8 @@ export const exportRulesRoute = (
|
|||
const siemResponse = buildSiemResponse(response);
|
||||
const rulesClient = (await context.alerting).getRulesClient();
|
||||
const exceptionsClient = (await context.lists)?.getExceptionListClient();
|
||||
const actionsClient = (await context.actions)?.getActionsClient();
|
||||
|
||||
const {
|
||||
getExporter,
|
||||
getClient,
|
||||
|
@ -81,7 +83,8 @@ export const exportRulesRoute = (
|
|||
request.body.objects,
|
||||
logger,
|
||||
actionsExporter,
|
||||
request
|
||||
request,
|
||||
actionsClient
|
||||
)
|
||||
: await getExportAll(
|
||||
rulesClient,
|
||||
|
@ -89,7 +92,8 @@ export const exportRulesRoute = (
|
|||
savedObjectsClient,
|
||||
logger,
|
||||
actionsExporter,
|
||||
request
|
||||
request,
|
||||
actionsClient
|
||||
);
|
||||
|
||||
const responseBody = request.query.exclude_export_details
|
||||
|
|
|
@ -27,16 +27,41 @@ import { requestContextMock } from '../../../routes/__mocks__/request_context';
|
|||
import { savedObjectsExporterMock } from '@kbn/core-saved-objects-import-export-server-mocks';
|
||||
import { mockRouter } from '@kbn/core-http-router-server-mocks';
|
||||
import { Readable } from 'stream';
|
||||
import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client.mock';
|
||||
|
||||
const exceptionsClient = getExceptionListClientMock();
|
||||
|
||||
const connectors = [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.slack',
|
||||
name: 'slack',
|
||||
config: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
referencedByCount: 1,
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
actionTypeId: '.email',
|
||||
name: 'Email (preconfigured)',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
referencedByCount: 1,
|
||||
},
|
||||
];
|
||||
describe('getExportAll', () => {
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
const { clients } = requestContextMock.createTools();
|
||||
const exporterMock = savedObjectsExporterMock.create();
|
||||
const requestMock = mockRouter.createKibanaRequest();
|
||||
const actionsClient = actionsClientMock.create();
|
||||
beforeEach(async () => {
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
|
||||
actionsClient.getAll.mockImplementation(async () => {
|
||||
return connectors;
|
||||
});
|
||||
});
|
||||
|
||||
test('it exports everything from the alerts client', async () => {
|
||||
|
@ -61,7 +86,8 @@ describe('getExportAll', () => {
|
|||
clients.savedObjectsClient,
|
||||
logger,
|
||||
exporterMock,
|
||||
requestMock
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const rulesJson = JSON.parse(exports.rulesNdjson);
|
||||
const detailsJson = JSON.parse(exports.exportDetails);
|
||||
|
@ -147,7 +173,8 @@ describe('getExportAll', () => {
|
|||
clients.savedObjectsClient,
|
||||
logger,
|
||||
exporterMock,
|
||||
requestMock
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
expect(exports).toEqual({
|
||||
rulesNdjson: '',
|
||||
|
@ -232,7 +259,8 @@ describe('getExportAll', () => {
|
|||
clients.savedObjectsClient,
|
||||
logger,
|
||||
exporterMockWithConnector as never,
|
||||
requestMock
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const rulesJson = JSON.parse(exports.rulesNdjson);
|
||||
const detailsJson = JSON.parse(exports.exportDetails);
|
||||
|
@ -328,4 +356,126 @@ describe('getExportAll', () => {
|
|||
version: 'WzE2MDYsMV0=',
|
||||
});
|
||||
});
|
||||
test('it will export rule without its action connectors as they are Preconfigured', async () => {
|
||||
const rulesClient = rulesClientMock.create();
|
||||
const result = getFindResultWithSingleHit();
|
||||
const alert = {
|
||||
...getRuleMock(getQueryRuleParams()),
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '456',
|
||||
params: {
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
actionTypeId: '.email',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
alert.params = {
|
||||
...alert.params,
|
||||
filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
|
||||
threat: getThreatMock(),
|
||||
meta: { someMeta: 'someField' },
|
||||
timelineId: 'some-timeline-id',
|
||||
timelineTitle: 'some-timeline-title',
|
||||
};
|
||||
result.data = [alert];
|
||||
rulesClient.find.mockResolvedValue(result);
|
||||
const readable = new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
const exporterMockWithConnector = {
|
||||
exportByObjects: () => jest.fn().mockReturnValueOnce(readable),
|
||||
|
||||
exportByTypes: jest.fn(),
|
||||
};
|
||||
const exports = await getExportAll(
|
||||
rulesClient,
|
||||
exceptionsClient,
|
||||
clients.savedObjectsClient,
|
||||
logger,
|
||||
exporterMockWithConnector as never,
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const rulesJson = JSON.parse(exports.rulesNdjson);
|
||||
const detailsJson = JSON.parse(exports.exportDetails);
|
||||
expect(rulesJson).toEqual({
|
||||
author: ['Elastic'],
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '456',
|
||||
params: {
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: '.email',
|
||||
},
|
||||
],
|
||||
building_block_type: 'default',
|
||||
created_at: '2019-12-13T16:40:33.400Z',
|
||||
updated_at: '2019-12-13T16:40:33.400Z',
|
||||
created_by: 'elastic',
|
||||
description: 'Detecting root and admin users',
|
||||
enabled: true,
|
||||
false_positives: [],
|
||||
filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
|
||||
from: 'now-6m',
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
immutable: false,
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
interval: '5m',
|
||||
rule_id: 'rule-1',
|
||||
language: 'kuery',
|
||||
license: 'Elastic License',
|
||||
output_index: '.siem-signals',
|
||||
max_signals: 10000,
|
||||
risk_score: 50,
|
||||
risk_score_mapping: [],
|
||||
name: 'Detect Root/Admin Users',
|
||||
query: 'user.name: root or user.name: admin',
|
||||
references: ['http://example.com', 'https://example.com'],
|
||||
related_integrations: [],
|
||||
required_fields: [],
|
||||
setup: '',
|
||||
timeline_id: 'some-timeline-id',
|
||||
timeline_title: 'some-timeline-title',
|
||||
meta: { someMeta: 'someField' },
|
||||
severity: 'high',
|
||||
severity_mapping: [],
|
||||
updated_by: 'elastic',
|
||||
tags: [],
|
||||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'rule',
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
revision: 0,
|
||||
exceptions_list: getListArrayMock(),
|
||||
});
|
||||
expect(detailsJson).toEqual({
|
||||
exported_exception_list_count: 1,
|
||||
exported_exception_list_item_count: 1,
|
||||
exported_count: 3,
|
||||
exported_rules_count: 1,
|
||||
missing_exception_list_item_count: 0,
|
||||
missing_exception_list_items: [],
|
||||
missing_exception_lists: [],
|
||||
missing_exception_lists_count: 0,
|
||||
missing_rules: [],
|
||||
missing_rules_count: 0,
|
||||
excluded_action_connection_count: 0,
|
||||
excluded_action_connections: [],
|
||||
exported_action_connector_count: 0,
|
||||
missing_action_connection_count: 0,
|
||||
missing_action_connections: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -10,6 +10,7 @@ import { transformDataToNdjson } from '@kbn/securitysolution-utils';
|
|||
import type { ISavedObjectsExporter, KibanaRequest, Logger } from '@kbn/core/server';
|
||||
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
|
||||
import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/server';
|
||||
import type { ActionsClient } from '@kbn/actions-plugin/server';
|
||||
import { getNonPackagedRules } from '../search/get_existing_prepackaged_rules';
|
||||
import { getExportDetailsNdjson } from './get_export_details_ndjson';
|
||||
import { transformAlertsToRules, transformRuleToExportableFormat } from '../../utils/utils';
|
||||
|
@ -25,7 +26,8 @@ export const getExportAll = async (
|
|||
savedObjectsClient: RuleExecutorServices['savedObjectsClient'],
|
||||
logger: Logger,
|
||||
actionsExporter: ISavedObjectsExporter,
|
||||
request: KibanaRequest
|
||||
request: KibanaRequest,
|
||||
actionsClient: ActionsClient
|
||||
): Promise<{
|
||||
rulesNdjson: string;
|
||||
exportDetails: string;
|
||||
|
@ -53,7 +55,8 @@ export const getExportAll = async (
|
|||
const { actionConnectors, actionConnectorDetails } = await getRuleActionConnectorsForExport(
|
||||
rules,
|
||||
actionsExporter,
|
||||
request
|
||||
request,
|
||||
actionsClient
|
||||
);
|
||||
|
||||
const rulesNdjson = transformDataToNdjson(exportRules);
|
||||
|
|
|
@ -28,19 +28,43 @@ import { mockRouter } from '@kbn/core-http-router-server-mocks';
|
|||
const exceptionsClient = getExceptionListClientMock();
|
||||
import type { loggingSystemMock } from '@kbn/core/server/mocks';
|
||||
import { requestContextMock } from '../../../routes/__mocks__/request_context';
|
||||
import { actionsClientMock } from '@kbn/actions-plugin/server/actions_client.mock';
|
||||
|
||||
const connectors = [
|
||||
{
|
||||
id: '123',
|
||||
actionTypeId: '.slack',
|
||||
name: 'slack',
|
||||
config: {},
|
||||
isPreconfigured: false,
|
||||
isDeprecated: false,
|
||||
referencedByCount: 1,
|
||||
},
|
||||
{
|
||||
id: '456',
|
||||
actionTypeId: '.email',
|
||||
name: 'Email (preconfigured)',
|
||||
config: {},
|
||||
isPreconfigured: true,
|
||||
isDeprecated: false,
|
||||
referencedByCount: 1,
|
||||
},
|
||||
];
|
||||
describe('get_export_by_object_ids', () => {
|
||||
let logger: ReturnType<typeof loggingSystemMock.createLogger>;
|
||||
const { clients } = requestContextMock.createTools();
|
||||
const exporterMock = savedObjectsExporterMock.create();
|
||||
const requestMock = mockRouter.createKibanaRequest();
|
||||
|
||||
const actionsClient = actionsClientMock.create();
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
jest.restoreAllMocks();
|
||||
jest.clearAllMocks();
|
||||
|
||||
clients.savedObjectsClient.find.mockResolvedValue(getEmptySavedObjectsResponse());
|
||||
actionsClient.getAll.mockImplementation(async () => {
|
||||
return connectors;
|
||||
});
|
||||
});
|
||||
|
||||
describe('getExportByObjectIds', () => {
|
||||
|
@ -56,7 +80,8 @@ describe('get_export_by_object_ids', () => {
|
|||
objects,
|
||||
logger,
|
||||
exporterMock,
|
||||
requestMock
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const exportsObj = {
|
||||
rulesNdjson: JSON.parse(exports.rulesNdjson),
|
||||
|
@ -151,7 +176,8 @@ describe('get_export_by_object_ids', () => {
|
|||
objects,
|
||||
logger,
|
||||
exporterMock,
|
||||
requestMock
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const details = getOutputDetailsSampleWithExceptions({
|
||||
missingRules: [{ rule_id: 'rule-1' }],
|
||||
|
@ -242,7 +268,8 @@ describe('get_export_by_object_ids', () => {
|
|||
objects,
|
||||
logger,
|
||||
exporterMockWithConnector as never,
|
||||
requestMock
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const rulesJson = JSON.parse(exports.rulesNdjson);
|
||||
const detailsJson = JSON.parse(exports.exportDetails);
|
||||
|
@ -338,6 +365,129 @@ describe('get_export_by_object_ids', () => {
|
|||
version: 'WzE2MDYsMV0=',
|
||||
});
|
||||
});
|
||||
test('it will export rule without its action connectors as they are Preconfigured', async () => {
|
||||
const rulesClient = rulesClientMock.create();
|
||||
const result = getFindResultWithSingleHit();
|
||||
const alert = {
|
||||
...getRuleMock(getQueryRuleParams()),
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '456',
|
||||
params: {
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
actionTypeId: '.email',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
alert.params = {
|
||||
...alert.params,
|
||||
filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
|
||||
threat: getThreatMock(),
|
||||
meta: { someMeta: 'someField' },
|
||||
timelineId: 'some-timeline-id',
|
||||
timelineTitle: 'some-timeline-title',
|
||||
};
|
||||
result.data = [alert];
|
||||
rulesClient.find.mockResolvedValue(result);
|
||||
const readable = new Readable({
|
||||
objectMode: true,
|
||||
read() {
|
||||
return null;
|
||||
},
|
||||
});
|
||||
const objects = [{ rule_id: 'rule-1' }];
|
||||
const exporterMockWithConnector = {
|
||||
exportByObjects: () => jest.fn().mockReturnValueOnce(readable),
|
||||
|
||||
exportByTypes: jest.fn(),
|
||||
};
|
||||
const exports = await getExportByObjectIds(
|
||||
rulesClient,
|
||||
exceptionsClient,
|
||||
clients.savedObjectsClient,
|
||||
objects,
|
||||
logger,
|
||||
exporterMockWithConnector as never,
|
||||
requestMock,
|
||||
actionsClient
|
||||
);
|
||||
const rulesJson = JSON.parse(exports.rulesNdjson);
|
||||
const detailsJson = JSON.parse(exports.exportDetails);
|
||||
expect(rulesJson).toEqual({
|
||||
author: ['Elastic'],
|
||||
actions: [
|
||||
{
|
||||
group: 'default',
|
||||
id: '456',
|
||||
params: {
|
||||
message: 'Rule {{context.rule.name}} generated {{state.signals_count}} alerts',
|
||||
},
|
||||
action_type_id: '.email',
|
||||
},
|
||||
],
|
||||
building_block_type: 'default',
|
||||
created_at: '2019-12-13T16:40:33.400Z',
|
||||
updated_at: '2019-12-13T16:40:33.400Z',
|
||||
created_by: 'elastic',
|
||||
description: 'Detecting root and admin users',
|
||||
enabled: true,
|
||||
false_positives: [],
|
||||
filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }],
|
||||
from: 'now-6m',
|
||||
id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd',
|
||||
immutable: false,
|
||||
index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'],
|
||||
interval: '5m',
|
||||
rule_id: 'rule-1',
|
||||
language: 'kuery',
|
||||
license: 'Elastic License',
|
||||
output_index: '.siem-signals',
|
||||
max_signals: 10000,
|
||||
risk_score: 50,
|
||||
risk_score_mapping: [],
|
||||
name: 'Detect Root/Admin Users',
|
||||
query: 'user.name: root or user.name: admin',
|
||||
references: ['http://example.com', 'https://example.com'],
|
||||
related_integrations: [],
|
||||
required_fields: [],
|
||||
setup: '',
|
||||
timeline_id: 'some-timeline-id',
|
||||
timeline_title: 'some-timeline-title',
|
||||
meta: { someMeta: 'someField' },
|
||||
severity: 'high',
|
||||
severity_mapping: [],
|
||||
updated_by: 'elastic',
|
||||
tags: [],
|
||||
to: 'now',
|
||||
type: 'query',
|
||||
threat: getThreatMock(),
|
||||
throttle: 'rule',
|
||||
note: '# Investigative notes',
|
||||
version: 1,
|
||||
revision: 0,
|
||||
exceptions_list: getListArrayMock(),
|
||||
});
|
||||
expect(detailsJson).toEqual({
|
||||
exported_exception_list_count: 0,
|
||||
exported_exception_list_item_count: 0,
|
||||
exported_count: 1,
|
||||
exported_rules_count: 1,
|
||||
missing_exception_list_item_count: 0,
|
||||
missing_exception_list_items: [],
|
||||
missing_exception_lists: [],
|
||||
missing_exception_lists_count: 0,
|
||||
missing_rules: [],
|
||||
missing_rules_count: 0,
|
||||
excluded_action_connection_count: 0,
|
||||
excluded_action_connections: [],
|
||||
exported_action_connector_count: 0,
|
||||
missing_action_connection_count: 0,
|
||||
missing_action_connections: [],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('getRulesFromObjects', () => {
|
||||
|
|
|
@ -13,6 +13,7 @@ import type { ISavedObjectsExporter, KibanaRequest, Logger } from '@kbn/core/ser
|
|||
import type { ExceptionListClient } from '@kbn/lists-plugin/server';
|
||||
import type { RulesClient, RuleExecutorServices } from '@kbn/alerting-plugin/server';
|
||||
|
||||
import type { ActionsClient } from '@kbn/actions-plugin/server';
|
||||
import { getExportDetailsNdjson } from './get_export_details_ndjson';
|
||||
|
||||
import { isAlertType } from '../../../rule_schema';
|
||||
|
@ -49,7 +50,8 @@ export const getExportByObjectIds = async (
|
|||
objects: Array<{ rule_id: string }>,
|
||||
logger: Logger,
|
||||
actionsExporter: ISavedObjectsExporter,
|
||||
request: KibanaRequest
|
||||
request: KibanaRequest,
|
||||
actionsClient: ActionsClient
|
||||
): Promise<{
|
||||
rulesNdjson: string;
|
||||
exportDetails: string;
|
||||
|
@ -73,7 +75,8 @@ export const getExportByObjectIds = async (
|
|||
const { actionConnectors, actionConnectorDetails } = await getRuleActionConnectorsForExport(
|
||||
rules,
|
||||
actionsExporter,
|
||||
request
|
||||
request,
|
||||
actionsClient
|
||||
);
|
||||
|
||||
const rulesNdjson = transformDataToNdjson(rules);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* 2.0; you may not use this file except in compliance with the Elastic License
|
||||
* 2.0.
|
||||
*/
|
||||
import type { ActionsClient } from '@kbn/actions-plugin/server';
|
||||
import type { KibanaRequest } from '@kbn/core-http-server';
|
||||
import type { SavedObjectTypeIdTuple } from '@kbn/core-saved-objects-common';
|
||||
import type {
|
||||
|
@ -42,11 +43,30 @@ const mapExportedActionConnectorsDetailsToDefault = (
|
|||
excluded_action_connections: exportDetails.excludedObjects,
|
||||
};
|
||||
};
|
||||
const filterOutPredefinedActionConnectorsIds = async (
|
||||
actionsClient: ActionsClient,
|
||||
actionsIdsToExport: string[]
|
||||
): Promise<string[]> => {
|
||||
const allActions = await actionsClient.getAll();
|
||||
const predefinedActionsIds = allActions
|
||||
.filter(({ isPreconfigured }) => isPreconfigured)
|
||||
.map(({ id }) => id);
|
||||
if (predefinedActionsIds.length)
|
||||
return actionsIdsToExport.filter((id) => !predefinedActionsIds.includes(id));
|
||||
return actionsIdsToExport;
|
||||
};
|
||||
|
||||
// This function is used to get, and return the exported actions' connectors'
|
||||
// using the ISavedObjectsExporter and it filters out any Preconfigured
|
||||
// Connectors as they shouldn't be exported, imported or changed
|
||||
// by the user, that's why the function also accepts the actionsClient
|
||||
// to getAll actions connectors
|
||||
|
||||
export const getRuleActionConnectorsForExport = async (
|
||||
rules: RuleResponse[],
|
||||
actionsExporter: ISavedObjectsExporter,
|
||||
request: KibanaRequest
|
||||
request: KibanaRequest,
|
||||
actionsClient: ActionsClient
|
||||
) => {
|
||||
const exportedActionConnectors: {
|
||||
actionConnectors: string;
|
||||
|
@ -56,7 +76,11 @@ export const getRuleActionConnectorsForExport = async (
|
|||
actionConnectorDetails: defaultActionConnectorDetails,
|
||||
};
|
||||
|
||||
const actionsIds = [...new Set(rules.flatMap((rule) => rule.actions.map(({ id }) => id)))];
|
||||
let actionsIds = [...new Set(rules.flatMap((rule) => rule.actions.map(({ id }) => id)))];
|
||||
if (!actionsIds.length) return exportedActionConnectors;
|
||||
|
||||
// handle preconfigured connectors
|
||||
actionsIds = await filterOutPredefinedActionConnectorsIds(actionsClient, actionsIds);
|
||||
|
||||
if (!actionsIds.length) return exportedActionConnectors;
|
||||
|
||||
|
|
|
@ -78,6 +78,20 @@ export function createTestConfig(options: CreateTestConfigOptions, testFiles?: s
|
|||
'previewTelemetryUrlEnabled',
|
||||
])}`,
|
||||
'--xpack.task_manager.poll_interval=1000',
|
||||
`--xpack.actions.preconfigured=${JSON.stringify({
|
||||
'my-test-email': {
|
||||
actionTypeId: '.email',
|
||||
name: 'TestEmail#xyz',
|
||||
config: {
|
||||
from: 'me@test.com',
|
||||
service: '__json',
|
||||
},
|
||||
secrets: {
|
||||
user: 'user',
|
||||
password: 'password',
|
||||
},
|
||||
},
|
||||
})}`,
|
||||
...(ssl
|
||||
? [
|
||||
`--elasticsearch.hosts=${servers.elasticsearch.protocol}://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`,
|
||||
|
|
|
@ -270,6 +270,117 @@ export default ({ getService }: FtrProviderContext): void => {
|
|||
expect(secondRule).toEqual(outputRule2);
|
||||
});
|
||||
|
||||
it('should export actions connectors with the rule', async () => {
|
||||
// create a new action
|
||||
const { body: hookAction } = await supertest
|
||||
.post('/api/actions/action')
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send(getWebHookAction())
|
||||
.expect(200);
|
||||
|
||||
const action = {
|
||||
group: 'default',
|
||||
id: hookAction.id,
|
||||
action_type_id: hookAction.actionTypeId,
|
||||
params: {},
|
||||
};
|
||||
|
||||
const rule1: ReturnType<typeof getSimpleRule> = {
|
||||
...getSimpleRule('rule-1'),
|
||||
actions: [action],
|
||||
};
|
||||
|
||||
await createRule(supertest, log, rule1);
|
||||
|
||||
const { body } = await supertest
|
||||
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200)
|
||||
.parse(binaryToString);
|
||||
|
||||
const connectorsObjectParsed = JSON.parse(body.toString().split(/\n/)[1]);
|
||||
const exportDetailsParsed = JSON.parse(body.toString().split(/\n/)[2]);
|
||||
|
||||
expect(connectorsObjectParsed).toEqual(
|
||||
expect.objectContaining({
|
||||
attributes: {
|
||||
actionTypeId: '.webhook',
|
||||
config: {
|
||||
hasAuth: true,
|
||||
headers: null,
|
||||
method: 'post',
|
||||
url: 'http://localhost',
|
||||
},
|
||||
isMissingSecrets: true,
|
||||
name: 'Some connector',
|
||||
secrets: {},
|
||||
},
|
||||
references: [],
|
||||
type: 'action',
|
||||
})
|
||||
);
|
||||
expect(exportDetailsParsed).toEqual({
|
||||
exported_exception_list_count: 0,
|
||||
exported_exception_list_item_count: 0,
|
||||
exported_count: 2,
|
||||
exported_rules_count: 1,
|
||||
missing_exception_list_item_count: 0,
|
||||
missing_exception_list_items: [],
|
||||
missing_exception_lists: [],
|
||||
missing_exception_lists_count: 0,
|
||||
missing_rules: [],
|
||||
missing_rules_count: 0,
|
||||
excluded_action_connection_count: 0,
|
||||
excluded_action_connections: [],
|
||||
exported_action_connector_count: 1,
|
||||
missing_action_connection_count: 0,
|
||||
missing_action_connections: [],
|
||||
});
|
||||
});
|
||||
it('should export rule without the action connector if it is Preconfigured Connector', async () => {
|
||||
const action = {
|
||||
group: 'default',
|
||||
id: 'my-test-email',
|
||||
action_type_id: '.email',
|
||||
params: {},
|
||||
};
|
||||
|
||||
const rule1: ReturnType<typeof getSimpleRule> = {
|
||||
...getSimpleRule('rule-1'),
|
||||
actions: [action],
|
||||
};
|
||||
|
||||
await createRule(supertest, log, rule1);
|
||||
|
||||
const { body } = await supertest
|
||||
.post(`${DETECTION_ENGINE_RULES_URL}/_export`)
|
||||
.set('kbn-xsrf', 'true')
|
||||
.send()
|
||||
.expect(200)
|
||||
.parse(binaryToString);
|
||||
|
||||
const exportDetailsParsed = JSON.parse(body.toString().split(/\n/)[1]);
|
||||
|
||||
expect(exportDetailsParsed).toEqual({
|
||||
exported_exception_list_count: 0,
|
||||
exported_exception_list_item_count: 0,
|
||||
exported_count: 1,
|
||||
exported_rules_count: 1,
|
||||
missing_exception_list_item_count: 0,
|
||||
missing_exception_list_items: [],
|
||||
missing_exception_lists: [],
|
||||
missing_exception_lists_count: 0,
|
||||
missing_rules: [],
|
||||
missing_rules_count: 0,
|
||||
excluded_action_connection_count: 0,
|
||||
excluded_action_connections: [],
|
||||
exported_action_connector_count: 0,
|
||||
missing_action_connection_count: 0,
|
||||
missing_action_connections: [],
|
||||
});
|
||||
});
|
||||
|
||||
/**
|
||||
* Tests the legacy actions to ensure we can export legacy notifications
|
||||
* @deprecated Once the legacy notification system is removed, remove this test too.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue