Skip to content

Commit

Permalink
[SIEM][CASE] Refactor Connectors - Jira Connector (#63450)
Browse files Browse the repository at this point in the history
  • Loading branch information
cnasikas committed Apr 30, 2020
1 parent 1a3d643 commit ea41ab1
Show file tree
Hide file tree
Showing 84 changed files with 5,330 additions and 3,235 deletions.
60 changes: 56 additions & 4 deletions x-pack/plugins/actions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Table of Contents
- [RESTful API](#restful-api)
- [`POST /api/action`: Create action](#post-apiaction-create-action)
- [`DELETE /api/action/{id}`: Delete action](#delete-apiactionid-delete-action)
- [`GET /api/action/_getAll`: Get all actions](#get-apiaction-get-all-actions)
- [`GET /api/action/_getAll`: Get all actions](#get-apiactiongetall-get-all-actions)
- [`GET /api/action/{id}`: Get action](#get-apiactionid-get-action)
- [`GET /api/action/types`: List action types](#get-apiactiontypes-list-action-types)
- [`PUT /api/action/{id}`: Update action](#put-apiactionid-update-action)
Expand Down Expand Up @@ -64,6 +64,12 @@ Table of Contents
- [`config`](#config-6)
- [`secrets`](#secrets-6)
- [`params`](#params-6)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice)
- [Jira](#jira)
- [`config`](#config-7)
- [`secrets`](#secrets-7)
- [`params`](#params-7)
- [`subActionParams (pushToService)`](#subactionparams-pushtoservice-1)
- [Command Line Utility](#command-line-utility)

## Terminology
Expand Down Expand Up @@ -143,8 +149,8 @@ This is the primary function for an action type. Whenever the action needs to ex
| actionId | The action saved object id that the action type is executing for. |
| config | The decrypted configuration given to an action. This comes from the action saved object that is partially or fully encrypted within the data store. If you would like to validate the config before being passed to the executor, define `validate.config` within the action type. |
| params | Parameters for the execution. These will be given at execution time by either an alert or manually provided when calling the plugin provided execute function. |
| services.callCluster(path, opts) | Use this to do Elasticsearch queries on the cluster Kibana connects to. This function is the same as any other `callCluster` in Kibana but runs in the context of the user who is calling the action when security is enabled.|
| services.getScopedCallCluster | This function scopes an instance of CallCluster by returning a `callCluster(path, opts)` function that runs in the context of the user who is calling the action when security is enabled. This must only be called with instances of CallCluster provided by core.|
| services.callCluster(path, opts) | Use this to do Elasticsearch queries on the cluster Kibana connects to. This function is the same as any other `callCluster` in Kibana but runs in the context of the user who is calling the action when security is enabled. |
| services.getScopedCallCluster | This function scopes an instance of CallCluster by returning a `callCluster(path, opts)` function that runs in the context of the user who is calling the action when security is enabled. This must only be called with instances of CallCluster provided by core. |
| services.savedObjectsClient | This is an instance of the saved objects client. This provides the ability to do CRUD on any saved objects within the same space the alert lives in.<br><br>The scope of the saved objects client is tied to the user in context calling the execute API or the API key provided to the execute plugin function (only when security isenabled). |
| services.log(tags, [data], [timestamp]) | Use this to create server logs. (This is the same function as server.log) |

Expand Down Expand Up @@ -483,13 +489,59 @@ The ServiceNow action uses the [V2 Table API](https://developer.servicenow.com/a

### `params`

| Property | Description | Type |
| --------------- | ------------------------------------------------------------------------------------ | ------ |
| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string |
| subActionParams | The parameters of the sub action | object |

#### `subActionParams (pushToService)`

| Property | Description | Type |
| ----------- | -------------------------------------------------------------------------------------------------------------------------- | --------------------- |
| caseId | The case id | string |
| title | The title of the case | string _(optional)_ |
| description | The description of the case | string _(optional)_ |
| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }` | object[] _(optional)_ |
| incidentID | The id of the incident in ServiceNow . If presented the incident will be update. Otherwise a new incident will be created. | string _(optional)_ |
| externalId | The id of the incident in ServiceNow . If presented the incident will be update. Otherwise a new incident will be created. | string _(optional)_ |

---

## Jira

ID: `.jira`

The Jira action uses the [V2 API](https://developer.atlassian.com/cloud/jira/platform/rest/v2/) to create and update Jira incidents.

### `config`

| Property | Description | Type |
| ------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------ |
| apiUrl | ServiceNow instance URL. | string |
| casesConfiguration | Case configuration object. The object should contain an attribute called `mapping`. A `mapping` is an array of objects. Each mapping object should be of the form `{ source: string, target: string, actionType: string }`. `source` is the Case field. `target` is the Jira field where `source` will be mapped to. `actionType` can be one of `nothing`, `overwrite` or `append`. For example the `{ source: 'title', target: 'summary', actionType: 'overwrite' }` record, inside mapping array, means that the title of a case will be mapped to the short description of an incident in ServiceNow and will be overwrite on each update. | object |

### `secrets`

| Property | Description | Type |
| -------- | --------------------------------------- | ------ |
| email | email for HTTP Basic authentication | string |
| apiToken | API token for HTTP Basic authentication | string |

### `params`

| Property | Description | Type |
| --------------- | ------------------------------------------------------------------------------------ | ------ |
| subAction | The sub action to perform. It can be `pushToService`, `handshake`, and `getIncident` | string |
| subActionParams | The parameters of the sub action | object |

#### `subActionParams (pushToService)`

| Property | Description | Type |
| ----------- | ------------------------------------------------------------------------------------------------------------------- | --------------------- |
| caseId | The case id | string |
| title | The title of the case | string _(optional)_ |
| description | The description of the case | string _(optional)_ |
| comments | The comments of the case. A comment is of the form `{ commentId: string, version: string, comment: string }` | object[] _(optional)_ |
| externalId | The id of the incident in Jira. If presented the incident will be update. Otherwise a new incident will be created. | string _(optional)_ |

# Command Line Utility

Expand Down
93 changes: 93 additions & 0 deletions x-pack/plugins/actions/server/builtin_action_types/case/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import {
ExternalServiceApi,
ExternalServiceParams,
PushToServiceResponse,
GetIncidentApiHandlerArgs,
HandshakeApiHandlerArgs,
PushToServiceApiHandlerArgs,
} from './types';
import { prepareFieldsForTransformation, transformFields, transformComments } from './utils';

const handshakeHandler = async ({
externalService,
mapping,
params,
}: HandshakeApiHandlerArgs) => {};
const getIncidentHandler = async ({
externalService,
mapping,
params,
}: GetIncidentApiHandlerArgs) => {};

const pushToServiceHandler = async ({
externalService,
mapping,
params,
}: PushToServiceApiHandlerArgs): Promise<PushToServiceResponse> => {
const { externalId, comments } = params;
const updateIncident = externalId ? true : false;
const defaultPipes = updateIncident ? ['informationUpdated'] : ['informationCreated'];
let currentIncident: ExternalServiceParams | undefined;
let res: PushToServiceResponse;

if (externalId) {
currentIncident = await externalService.getIncident(externalId);
}

const fields = prepareFieldsForTransformation({
params,
mapping,
defaultPipes,
});

const incident = transformFields({
params,
fields,
currentIncident,
});

if (updateIncident) {
res = await externalService.updateIncident({ incidentId: externalId, incident });
} else {
res = await externalService.createIncident({ incident });
}

if (
comments &&
Array.isArray(comments) &&
comments.length > 0 &&
mapping.get('comments')?.actionType !== 'nothing'
) {
const commentsTransformed = transformComments(comments, ['informationAdded']);

res.comments = [];
for (const currentComment of commentsTransformed) {
const comment = await externalService.createComment({
incidentId: res.id,
comment: currentComment,
field: mapping.get('comments')?.target ?? 'comments',
});
res.comments = [
...(res.comments ?? []),
{
commentId: comment.commentId,
pushedDate: comment.pushedDate,
},
];
}
}

return res;
};

export const api: ExternalServiceApi = {
handshake: handshakeHandler,
pushToService: pushToServiceHandler,
getIncident: getIncidentHandler,
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,4 @@
* you may not use this file except in compliance with the Elastic License.
*/

export const ACTION_TYPE_ID = '.servicenow';
export const SUPPORTED_SOURCE_FIELDS = ['title', 'comments', 'description'];
98 changes: 98 additions & 0 deletions x-pack/plugins/actions/server/builtin_action_types/case/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

import { schema } from '@kbn/config-schema';

export const MappingActionType = schema.oneOf([
schema.literal('nothing'),
schema.literal('overwrite'),
schema.literal('append'),
]);

export const MapRecordSchema = schema.object({
source: schema.string(),
target: schema.string(),
actionType: MappingActionType,
});

export const CaseConfigurationSchema = schema.object({
mapping: schema.arrayOf(MapRecordSchema),
});

export const ExternalIncidentServiceConfiguration = {
apiUrl: schema.string(),
casesConfiguration: CaseConfigurationSchema,
};

export const ExternalIncidentServiceConfigurationSchema = schema.object(
ExternalIncidentServiceConfiguration
);

export const ExternalIncidentServiceSecretConfiguration = {
password: schema.string(),
username: schema.string(),
};

export const ExternalIncidentServiceSecretConfigurationSchema = schema.object(
ExternalIncidentServiceSecretConfiguration
);

export const UserSchema = schema.object({
fullName: schema.nullable(schema.string()),
username: schema.nullable(schema.string()),
});

const EntityInformation = {
createdAt: schema.string(),
createdBy: UserSchema,
updatedAt: schema.nullable(schema.string()),
updatedBy: schema.nullable(UserSchema),
};

export const EntityInformationSchema = schema.object(EntityInformation);

export const CommentSchema = schema.object({
commentId: schema.string(),
comment: schema.string(),
...EntityInformation,
});

export const ExecutorSubActionSchema = schema.oneOf([
schema.literal('getIncident'),
schema.literal('pushToService'),
schema.literal('handshake'),
]);

export const ExecutorSubActionPushParamsSchema = schema.object({
caseId: schema.string(),
title: schema.string(),
description: schema.nullable(schema.string()),
comments: schema.nullable(schema.arrayOf(CommentSchema)),
externalId: schema.nullable(schema.string()),
...EntityInformation,
});

export const ExecutorSubActionGetIncidentParamsSchema = schema.object({
externalId: schema.string(),
});

// Reserved for future implementation
export const ExecutorSubActionHandshakeParamsSchema = schema.object({});

export const ExecutorParamsSchema = schema.oneOf([
schema.object({
subAction: schema.literal('getIncident'),
subActionParams: ExecutorSubActionGetIncidentParamsSchema,
}),
schema.object({
subAction: schema.literal('handshake'),
subActionParams: ExecutorSubActionHandshakeParamsSchema,
}),
schema.object({
subAction: schema.literal('pushToService'),
subActionParams: ExecutorSubActionPushParamsSchema,
}),
]);
Loading

0 comments on commit ea41ab1

Please sign in to comment.