Skip to content

Commit

Permalink
Register ServiceNow SIR action
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Jan 14, 2021
1 parent c4056e7 commit a192e05
Show file tree
Hide file tree
Showing 17 changed files with 230 additions and 121 deletions.
8 changes: 5 additions & 3 deletions x-pack/plugins/actions/server/builtin_action_types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { getActionType as getPagerDutyActionType } from './pagerduty';
import { getActionType as getServerLogActionType } from './server_log';
import { getActionType as getSlackActionType } from './slack';
import { getActionType as getWebhookActionType } from './webhook';
import { getActionType as getServiceNowActionType } from './servicenow';
import { getServiceNowIMActionType, getServiceNowSIRActionType } from './servicenow';
import { getActionType as getJiraActionType } from './jira';
import { getActionType as getResilientActionType } from './resilient';
import { getActionType as getTeamsActionType } from './teams';
Expand All @@ -38,7 +38,8 @@ export {
} from './webhook';
export {
ActionParamsType as ServiceNowActionParams,
ActionTypeId as ServiceNowActionTypeId,
ServiceNowIMActionTypeId,
ServiceNowSIRActionTypeId,
} from './servicenow';
export { ActionParamsType as JiraActionParams, ActionTypeId as JiraActionTypeId } from './jira';
export {
Expand Down Expand Up @@ -66,7 +67,8 @@ export function registerBuiltInActionTypes({
actionTypeRegistry.register(getServerLogActionType({ logger }));
actionTypeRegistry.register(getSlackActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getWebhookActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowIMActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getServiceNowSIRActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getJiraActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getResilientActionType({ logger, configurationUtilities }));
actionTypeRegistry.register(getTeamsActionType({ logger, configurationUtilities }));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import { validate } from './validators';
import {
ExternalIncidentServiceConfiguration,
ExternalIncidentServiceSecretConfiguration,
ExecutorParamsSchema,
ExecutorParamsSchemaIM,
ExecutorParamsSchemaSIR,
} from './schema';
import { ActionsConfigurationUtilities } from '../../actions_config';
import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../../types';
Expand All @@ -29,18 +30,49 @@ import {
ServiceNowExecutorResultData,
} from './types';

export type ActionParamsType = TypeOf<typeof ExecutorParamsSchema>;
export type ActionParamsType =
| TypeOf<typeof ExecutorParamsSchemaIM>
| TypeOf<typeof ExecutorParamsSchemaSIR>;

interface GetActionTypeParams {
logger: Logger;
configurationUtilities: ActionsConfigurationUtilities;
}

const serviceNowIncidentTable = 'incident';
const serviceNowIMTable = 'incident';
const serviceNowSIRTable = 'sn_si_incident';

export const ServiceNowIMActionTypeId = '.servicenow';
export const ServiceNowSIRActionTypeId = '.servicenow-sir';

export const ActionTypeId = '.servicenow';
// action type definition
export function getActionType(
export function getServiceNowIMActionType(
params: GetActionTypeParams
): ActionType<
ServiceNowPublicConfigurationType,
ServiceNowSecretConfigurationType,
ExecutorParams,
PushToServiceResponse | {}
> {
const { logger, configurationUtilities } = params;
return {
id: ServiceNowIMActionTypeId,
minimumLicenseRequired: 'platinum',
name: i18n.SERVICENOW,
validate: {
config: schema.object(ExternalIncidentServiceConfiguration, {
validate: curry(validate.config)(configurationUtilities),
}),
secrets: schema.object(ExternalIncidentServiceSecretConfiguration, {
validate: curry(validate.secrets)(configurationUtilities),
}),
params: ExecutorParamsSchemaIM,
},
executor: curry(executor)({ logger, table: serviceNowIMTable }),
};
}

export function getServiceNowSIRActionType(
params: GetActionTypeParams
): ActionType<
ServiceNowPublicConfigurationType,
Expand All @@ -50,19 +82,19 @@ export function getActionType(
> {
const { logger, configurationUtilities } = params;
return {
id: ActionTypeId,
id: ServiceNowSIRActionTypeId,
minimumLicenseRequired: 'platinum',
name: i18n.NAME,
name: i18n.SERVICENOW_SIR,
validate: {
config: schema.object(ExternalIncidentServiceConfiguration, {
validate: curry(validate.config)(configurationUtilities),
}),
secrets: schema.object(ExternalIncidentServiceSecretConfiguration, {
validate: curry(validate.secrets)(configurationUtilities),
}),
params: ExecutorParamsSchema,
params: ExecutorParamsSchemaSIR,
},
executor: curry(executor)({ logger, table: serviceNowIncidentTable }),
executor: curry(executor)({ logger, table: serviceNowSIRTable }),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* you may not use this file except in compliance with the Elastic License.
*/

import { ExternalService, PushToServiceApiParams, ExecutorSubActionPushParams } from './types';
import { ExternalService, ExecutorSubActionPushParams } from './types';

export const serviceNowCommonFields = [
{
Expand Down Expand Up @@ -89,8 +89,6 @@ const executorParams: ExecutorSubActionPushParams = {
],
};

const apiParams: PushToServiceApiParams = {
...executorParams,
};
const apiParams = executorParams;

export { externalServiceMock, executorParams, apiParams };
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,45 @@ export const ExecutorSubActionSchema = schema.oneOf([
schema.literal('handshake'),
]);

export const ExecutorSubActionPushParamsSchema = schema.object({
const CommentsSchema = schema.nullable(
schema.arrayOf(
schema.object({
comment: schema.string(),
commentId: schema.string(),
})
)
);

const CommonAttributes = {
short_description: schema.string(),
description: schema.nullable(schema.string()),
externalId: schema.nullable(schema.string()),
};

// Schema for ServiceNow Incident Management (IM)
export const ExecutorSubActionPushParamsSchemaIM = schema.object({
incident: schema.object({
short_description: schema.string(),
description: schema.nullable(schema.string()),
externalId: schema.nullable(schema.string()),
...CommonAttributes,
severity: schema.nullable(schema.string()),
urgency: schema.nullable(schema.string()),
impact: schema.nullable(schema.string()),
}),
comments: schema.nullable(
schema.arrayOf(
schema.object({
comment: schema.string(),
commentId: schema.string(),
})
)
),
comments: CommentsSchema,
});

// Schema for ServiceNow Security Incident Response (SIR)
export const ExecutorSubActionPushParamsSchemaSIR = schema.object({
incident: schema.object({
...CommonAttributes,
category: schema.nullable(schema.string()),
dest_ip: schema.nullable(schema.string()),
malware_hash: schema.nullable(schema.string()),
malware_url: schema.nullable(schema.string()),
priority: schema.nullable(schema.string()),
source_ip: schema.nullable(schema.string()),
subcategory: schema.nullable(schema.string()),
}),
comments: CommentsSchema,
});

export const ExecutorSubActionGetIncidentParamsSchema = schema.object({
Expand All @@ -57,7 +79,28 @@ export const ExecutorSubActionGetIncidentParamsSchema = schema.object({
export const ExecutorSubActionHandshakeParamsSchema = schema.object({});
export const ExecutorSubActionCommonFieldsParamsSchema = schema.object({});

export const ExecutorParamsSchema = schema.oneOf([
// Executor parameters for ServiceNow Incident Management (IM)
export const ExecutorParamsSchemaIM = schema.oneOf([
schema.object({
subAction: schema.literal('getFields'),
subActionParams: ExecutorSubActionCommonFieldsParamsSchema,
}),
schema.object({
subAction: schema.literal('getIncident'),
subActionParams: ExecutorSubActionGetIncidentParamsSchema,
}),
schema.object({
subAction: schema.literal('handshake'),
subActionParams: ExecutorSubActionHandshakeParamsSchema,
}),
schema.object({
subAction: schema.literal('pushToService'),
subActionParams: ExecutorSubActionPushParamsSchemaIM,
}),
]);

// Executor parameters for ServiceNow Security Incident Response (SIR)
export const ExecutorParamsSchemaSIR = schema.oneOf([
schema.object({
subAction: schema.literal('getFields'),
subActionParams: ExecutorSubActionCommonFieldsParamsSchema,
Expand All @@ -72,6 +115,6 @@ export const ExecutorParamsSchema = schema.oneOf([
}),
schema.object({
subAction: schema.literal('pushToService'),
subActionParams: ExecutorSubActionPushParamsSchema,
subActionParams: ExecutorSubActionPushParamsSchemaSIR,
}),
]);
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ export const createExternalService = (
const { username, password } = secrets as ServiceNowSecretConfigurationType;

if (!url || !username || !password) {
throw Error(`[Action]${i18n.NAME}: Wrong configuration.`);
throw Error(`[Action]${i18n.SERVICENOW}: Wrong configuration.`);
}

const urlWithoutTrailingSlash = url.endsWith('/') ? url.slice(0, -1) : url;
Expand Down Expand Up @@ -62,7 +62,10 @@ export const createExternalService = (
return { ...res.data.result };
} catch (error) {
throw new Error(
getErrorMessage(i18n.NAME, `Unable to get incident with id ${id}. Error: ${error.message}`)
getErrorMessage(
i18n.SERVICENOW,
`Unable to get incident with id ${id}. Error: ${error.message}`
)
);
}
};
Expand All @@ -80,7 +83,10 @@ export const createExternalService = (
return res.data.result.length > 0 ? { ...res.data.result } : undefined;
} catch (error) {
throw new Error(
getErrorMessage(i18n.NAME, `Unable to find incidents by query. Error: ${error.message}`)
getErrorMessage(
i18n.SERVICENOW,
`Unable to find incidents by query. Error: ${error.message}`
)
);
}
};
Expand All @@ -104,7 +110,7 @@ export const createExternalService = (
};
} catch (error) {
throw new Error(
getErrorMessage(i18n.NAME, `Unable to create incident. Error: ${error.message}`)
getErrorMessage(i18n.SERVICENOW, `Unable to create incident. Error: ${error.message}`)
);
}
};
Expand All @@ -128,7 +134,7 @@ export const createExternalService = (
} catch (error) {
throw new Error(
getErrorMessage(
i18n.NAME,
i18n.SERVICENOW,
`Unable to update incident with id ${incidentId}. Error: ${error.message}`
)
);
Expand All @@ -146,7 +152,9 @@ export const createExternalService = (
checkInstance(res);
return res.data.result.length > 0 ? res.data.result : [];
} catch (error) {
throw new Error(getErrorMessage(i18n.NAME, `Unable to get fields. Error: ${error.message}`));
throw new Error(
getErrorMessage(i18n.SERVICENOW, `Unable to get fields. Error: ${error.message}`)
);
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

import { i18n } from '@kbn/i18n';

export const NAME = i18n.translate('xpack.actions.builtin.servicenowTitle', {
export const SERVICENOW = i18n.translate('xpack.actions.builtin.servicenowTitle', {
defaultMessage: 'ServiceNow',
});

export const SERVICENOW_SIR = i18n.translate('xpack.actions.builtin.serviceNowSIRTitle', {
defaultMessage: 'ServiceNow SIR',
});

export const ALLOWED_HOSTS_ERROR = (message: string) =>
i18n.translate('xpack.actions.builtin.configuration.apiAllowedHostsError', {
defaultMessage: 'error configuring connector action: {message}',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@

import { TypeOf } from '@kbn/config-schema';
import {
ExecutorParamsSchema,
ExecutorParamsSchemaIM,
ExecutorSubActionCommonFieldsParamsSchema,
ExecutorSubActionGetIncidentParamsSchema,
ExecutorSubActionHandshakeParamsSchema,
ExecutorSubActionPushParamsSchema,
ExecutorSubActionPushParamsSchemaIM,
ExternalIncidentServiceConfigurationSchema,
ExternalIncidentServiceSecretConfigurationSchema,
ExecutorParamsSchemaSIR,
ExecutorSubActionPushParamsSchemaSIR,
} from './schema';
import { ActionsConfigurationUtilities } from '../../actions_config';
import { Logger } from '../../../../../../src/core/server';
Expand All @@ -36,8 +38,16 @@ export interface CreateCommentRequest {
[key: string]: string;
}

export type ExecutorParams = TypeOf<typeof ExecutorParamsSchema>;
export type ExecutorSubActionPushParams = TypeOf<typeof ExecutorSubActionPushParamsSchema>;
export type ExecutorParams =
| TypeOf<typeof ExecutorParamsSchemaIM>
| TypeOf<typeof ExecutorParamsSchemaSIR>;

export type ExecutorSubActionPushParamsIM = TypeOf<typeof ExecutorSubActionPushParamsSchemaIM>;
export type ExecutorSubActionPushParamsSIR = TypeOf<typeof ExecutorSubActionPushParamsSchemaSIR>;

export type ExecutorSubActionPushParams =
| ExecutorSubActionPushParamsIM
| ExecutorSubActionPushParamsSIR;

export interface ExternalServiceCredentials {
config: Record<string, unknown>;
Expand Down Expand Up @@ -83,7 +93,9 @@ export type ExecutorSubActionHandshakeParams = TypeOf<
typeof ExecutorSubActionHandshakeParamsSchema
>;

export type Incident = Omit<ExecutorSubActionPushParams['incident'], 'externalId'>;
export type Incident =
| Omit<TypeOf<typeof ExecutorSubActionPushParamsSchemaIM>['incident'], 'externalId'>
| Omit<TypeOf<typeof ExecutorSubActionPushParamsSchemaSIR>['incident'], 'externalId'>;

export interface PushToServiceApiHandlerArgs extends ExternalServiceApiHandlerArgs {
params: PushToServiceApiParams;
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/actions/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ export type {
WebhookActionTypeId,
WebhookActionParams,
ServiceNowActionTypeId,
ServiceNowSIRActionTypeId,
ServiceNowActionParams,
JiraActionTypeId,
JiraActionParams,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { getPagerDutyActionType } from './pagerduty';
import { getWebhookActionType } from './webhook';
import { TypeRegistry } from '../../type_registry';
import { ActionTypeModel } from '../../../types';
import { getServiceNowActionType } from './servicenow';
import { getServiceNowIMActionType, getServiceNowSIRActionType } from './servicenow';
import { getJiraActionType } from './jira';
import { getResilientActionType } from './resilient';
import { getTeamsActionType } from './teams';
Expand All @@ -28,7 +28,8 @@ export function registerBuiltInActionTypes({
actionTypeRegistry.register(getIndexActionType());
actionTypeRegistry.register(getPagerDutyActionType());
actionTypeRegistry.register(getWebhookActionType());
actionTypeRegistry.register(getServiceNowActionType());
actionTypeRegistry.register(getServiceNowIMActionType());
actionTypeRegistry.register(getServiceNowSIRActionType());
actionTypeRegistry.register(getJiraActionType());
actionTypeRegistry.register(getResilientActionType());
actionTypeRegistry.register(getTeamsActionType());
Expand Down
Loading

0 comments on commit a192e05

Please sign in to comment.