Skip to content

Commit

Permalink
[SECURITY_SOLUTION][ENDPOINT] Trusted Apps Create API (#76178)
Browse files Browse the repository at this point in the history
* Create Trusted App API
  • Loading branch information
paul-tavares authored Sep 1, 2020
1 parent b2be910 commit 1c234bf
Show file tree
Hide file tree
Showing 8 changed files with 404 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ export const LIMITED_CONCURRENCY_ENDPOINT_ROUTE_TAG = 'endpoint:limited-concurre
export const LIMITED_CONCURRENCY_ENDPOINT_COUNT = 100;

export const TRUSTED_APPS_LIST_API = '/api/endpoint/trusted_apps';
export const TRUSTED_APPS_CREATE_API = '/api/endpoint/trusted_apps';
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 { GetTrustedAppsRequestSchema } from './trusted_apps';
import { GetTrustedAppsRequestSchema, PostTrustedAppCreateRequestSchema } from './trusted_apps';

describe('When invoking Trusted Apps Schema', () => {
describe('for GET List', () => {
Expand Down Expand Up @@ -68,4 +68,180 @@ describe('When invoking Trusted Apps Schema', () => {
});
});
});

describe('for POST Create', () => {
const getCreateTrustedAppItem = () => ({
name: 'Some Anti-Virus App',
description: 'this one is ok',
os: 'windows',
entries: [
{
field: 'path',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
},
],
});
const body = PostTrustedAppCreateRequestSchema.body;

it('should not error on a valid message', () => {
const bodyMsg = getCreateTrustedAppItem();
expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg);
});

it('should validate `name` is required', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
name: undefined,
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `name` value to be non-empty', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
name: '',
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `description` as optional', () => {
const { description, ...bodyMsg } = getCreateTrustedAppItem();
expect(body.validate(bodyMsg)).toStrictEqual(bodyMsg);
});

it('should validate `description` to be non-empty if defined', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
description: '',
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `os` to to only accept known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
os: undefined,
};
expect(() => body.validate(bodyMsg)).toThrow();

const bodyMsg2 = {
...bodyMsg,
os: '',
};
expect(() => body.validate(bodyMsg2)).toThrow();

const bodyMsg3 = {
...bodyMsg,
os: 'winz',
};
expect(() => body.validate(bodyMsg3)).toThrow();

['linux', 'macos', 'windows'].forEach((os) => {
expect(() => {
body.validate({
...bodyMsg,
os,
});
}).not.toThrow();
});
});

it('should validate `entries` as required', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: undefined,
};
expect(() => body.validate(bodyMsg)).toThrow();

const { entries, ...bodyMsg2 } = getCreateTrustedAppItem();
expect(() => body.validate(bodyMsg2)).toThrow();
});

it('should validate `entries` to have at least 1 item', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [],
};
expect(() => body.validate(bodyMsg)).toThrow();
});

describe('when `entries` are defined', () => {
const getTrustedAppItemEntryItem = () => getCreateTrustedAppItem().entries[0];

it('should validate `entry.field` is required', () => {
const { field, ...entry } = getTrustedAppItemEntryItem();
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [entry],
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `entry.field` is limited to known values', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
field: '',
},
],
};
expect(() => body.validate(bodyMsg)).toThrow();

const bodyMsg2 = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
field: 'invalid value',
},
],
};
expect(() => body.validate(bodyMsg2)).toThrow();

['hash', 'path'].forEach((field) => {
const bodyMsg3 = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
field,
},
],
};

expect(() => body.validate(bodyMsg3)).not.toThrow();
});
});

it.todo('should validate `entry.type` is limited to known values');

it.todo('should validate `entry.operator` is limited to known values');

it('should validate `entry.value` required', () => {
const { value, ...entry } = getTrustedAppItemEntryItem();
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [entry],
};
expect(() => body.validate(bodyMsg)).toThrow();
});

it('should validate `entry.value` is non-empty', () => {
const bodyMsg = {
...getCreateTrustedAppItem(),
entries: [
{
...getTrustedAppItemEntryItem(),
value: '',
},
],
};
expect(() => body.validate(bodyMsg)).toThrow();
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,20 @@ export const GetTrustedAppsRequestSchema = {
per_page: schema.maybe(schema.number({ defaultValue: 20, min: 1 })),
}),
};

export const PostTrustedAppCreateRequestSchema = {
body: schema.object({
name: schema.string({ minLength: 1 }),
description: schema.maybe(schema.string({ minLength: 1 })),
os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]),
entries: schema.arrayOf(
schema.object({
field: schema.oneOf([schema.literal('hash'), schema.literal('path')]),
type: schema.literal('match'),
operator: schema.literal('included'),
value: schema.string({ minLength: 1 }),
}),
{ minSize: 1 }
),
}),
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@
*/

import { TypeOf } from '@kbn/config-schema';
import { GetTrustedAppsRequestSchema } from '../schema/trusted_apps';
import {
GetTrustedAppsRequestSchema,
PostTrustedAppCreateRequestSchema,
} from '../schema/trusted_apps';

/** API request params for retrieving a list of Trusted Apps */
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>;
Expand All @@ -16,6 +19,12 @@ export interface GetTrustedListAppsResponse {
data: TrustedApp[];
}

/** API Request body for creating a new Trusted App entry */
export type PostTrustedAppCreateRequest = TypeOf<typeof PostTrustedAppCreateRequestSchema.body>;
export interface PostTrustedAppCreateResponse {
data: TrustedApp;
}

interface MacosLinuxConditionEntry {
field: 'hash' | 'path';
type: 'match';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ import { RequestHandler } from 'kibana/server';
import {
GetTrustedAppsListRequest,
GetTrustedListAppsResponse,
PostTrustedAppCreateRequest,
} from '../../../../common/endpoint/types';
import { EndpointAppContext } from '../../types';
import { exceptionItemToTrustedAppItem } from './utils';
import { exceptionItemToTrustedAppItem, newTrustedAppItemToExceptionItem } from './utils';
import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/constants';

export const getTrustedAppsListRouteHandler = (
Expand All @@ -24,7 +25,7 @@ export const getTrustedAppsListRouteHandler = (

try {
// Ensure list is created if it does not exist
await exceptionsListService?.createTrustedAppsList();
await exceptionsListService.createTrustedAppsList();
const results = await exceptionsListService.findExceptionListItem({
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
page,
Expand All @@ -47,3 +48,32 @@ export const getTrustedAppsListRouteHandler = (
}
};
};

export const getTrustedAppsCreateRouteHandler = (
endpointAppContext: EndpointAppContext
): RequestHandler<undefined, undefined, PostTrustedAppCreateRequest> => {
const logger = endpointAppContext.logFactory.get('trusted_apps');

return async (constext, req, res) => {
const exceptionsListService = endpointAppContext.service.getExceptionsList();
const newTrustedApp = req.body;

try {
// Ensure list is created if it does not exist
await exceptionsListService.createTrustedAppsList();

const createdTrustedAppExceptionItem = await exceptionsListService.createExceptionListItem(
newTrustedAppItemToExceptionItem(newTrustedApp)
);

return res.ok({
body: {
data: exceptionItemToTrustedAppItem(createdTrustedAppExceptionItem),
},
});
} catch (error) {
logger.error(error);
return res.internalError({ body: error });
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,15 @@
*/

import { IRouter } from 'kibana/server';
import { GetTrustedAppsRequestSchema } from '../../../../common/endpoint/schema/trusted_apps';
import { TRUSTED_APPS_LIST_API } from '../../../../common/endpoint/constants';
import { getTrustedAppsListRouteHandler } from './handlers';
import {
GetTrustedAppsRequestSchema,
PostTrustedAppCreateRequestSchema,
} from '../../../../common/endpoint/schema/trusted_apps';
import {
TRUSTED_APPS_CREATE_API,
TRUSTED_APPS_LIST_API,
} from '../../../../common/endpoint/constants';
import { getTrustedAppsCreateRouteHandler, getTrustedAppsListRouteHandler } from './handlers';
import { EndpointAppContext } from '../../types';

export const registerTrustedAppsRoutes = (
Expand All @@ -23,4 +29,14 @@ export const registerTrustedAppsRoutes = (
},
getTrustedAppsListRouteHandler(endpointAppContext)
);

// CREATE
router.post(
{
path: TRUSTED_APPS_CREATE_API,
validate: PostTrustedAppCreateRequestSchema,
options: { authRequired: true },
},
getTrustedAppsCreateRouteHandler(endpointAppContext)
);
};
Loading

0 comments on commit 1c234bf

Please sign in to comment.