Skip to content

Commit

Permalink
[SECURITY_SOLUTION][ENDPOINT] Create Trusted Apps API changes to proc…
Browse files Browse the repository at this point in the history
…ess user input (#78079) (#78211)

* Convert new trusted app data to expected format for artifact
* Renamed condition field `process.path` to `process.path.text`
* determine hash type based on length of hash value
* Convert `process.hash.[sha1|md5|sha256]` to `process.hash.*` for return on list api
* Add test for conversion of ExceptionItem to TrustedApp Item
  • Loading branch information
paul-tavares committed Sep 23, 2020
1 parent d35dfb0 commit 8a5a592
Show file tree
Hide file tree
Showing 6 changed files with 279 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ describe('When invoking Trusted Apps Schema', () => {
os: 'windows',
entries: [
{
field: 'process.path',
field: 'process.path.text',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
Expand Down Expand Up @@ -194,7 +194,7 @@ describe('When invoking Trusted Apps Schema', () => {
};
expect(() => body.validate(bodyMsg2)).toThrow();

['process.hash.*', 'process.path'].forEach((field) => {
['process.hash.*', 'process.path.text'].forEach((field) => {
const bodyMsg3 = {
...getCreateTrustedAppItem(),
entries: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ export const PostTrustedAppCreateRequestSchema = {
os: schema.oneOf([schema.literal('linux'), schema.literal('macos'), schema.literal('windows')]),
entries: schema.arrayOf(
schema.object({
field: schema.oneOf([schema.literal('process.hash.*'), schema.literal('process.path')]),
field: schema.oneOf([
schema.literal('process.hash.*'),
schema.literal('process.path.text'),
]),
type: schema.literal('match'),
operator: schema.literal('included'),
value: schema.string({ minLength: 1 }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {

/** API request params for retrieving a list of Trusted Apps */
export type GetTrustedAppsListRequest = TypeOf<typeof GetTrustedAppsRequestSchema.query>;

export interface GetTrustedListAppsResponse {
per_page: number;
page: number;
Expand All @@ -21,12 +22,13 @@ export interface GetTrustedListAppsResponse {

/** API Request body for creating a new Trusted App entry */
export type PostTrustedAppCreateRequest = TypeOf<typeof PostTrustedAppCreateRequestSchema.body>;

export interface PostTrustedAppCreateResponse {
data: TrustedApp;
}

export interface MacosLinuxConditionEntry {
field: 'process.hash.*' | 'process.path';
field: 'process.hash.*' | 'process.path.text';
type: 'match';
operator: 'included';
value: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ export const ConditionEntry = memo<ConditionEntryProps>(
'xpack.securitySolution.trustedapps.logicalConditionBuilder.entry.field.path',
{ defaultMessage: 'Path' }
),
value: 'process.path',
value: 'process.path.text',
},
];
}, []);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,10 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/const
import { EndpointAppContext } from '../../types';
import { ExceptionListClient, ListClient } from '../../../../../lists/server';
import { listMock } from '../../../../../lists/server/mocks';
import { ExceptionListItemSchema } from '../../../../../lists/common/schemas/response';
import {
ExceptionListItemSchema,
FoundExceptionListItemSchema,
} from '../../../../../lists/common/schemas/response';
import { DeleteTrustedAppsRequestParams } from './types';
import { getExceptionListItemSchemaMock } from '../../../../../lists/common/schemas/response/exception_list_item_schema.mock';

Expand Down Expand Up @@ -125,6 +128,97 @@ describe('when invoking endpoint trusted apps route handlers', () => {
});
});

it('should map Exception List Item to Trusted App item', async () => {
const request = createListRequest(10, 100);
const emptyResponse: FoundExceptionListItemSchema = {
data: [
{
_tags: ['os:windows'],
_version: undefined,
comments: [],
created_at: '2020-09-21T19:43:48.240Z',
created_by: 'test',
description: '',
entries: [
{
field: 'process.hash.sha256',
operator: 'included',
type: 'match',
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
},
{
field: 'process.hash.sha1',
operator: 'included',
type: 'match',
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
},
{
field: 'process.hash.md5',
operator: 'included',
type: 'match',
value: '741462ab431a22233c787baab9b653c7',
},
],
id: '1',
item_id: '11',
list_id: 'trusted apps test',
meta: undefined,
name: 'test',
namespace_type: 'agnostic',
tags: [],
tie_breaker_id: '1',
type: 'simple',
updated_at: '2020-09-21T19:43:48.240Z',
updated_by: 'test',
},
],
page: 10,
per_page: 100,
total: 0,
};

exceptionsListClient.findExceptionListItem.mockResolvedValue(emptyResponse);
await routeHandler(context, request, response);

expect(response.ok).toHaveBeenCalledWith({
body: {
data: [
{
created_at: '2020-09-21T19:43:48.240Z',
created_by: 'test',
description: '',
entries: [
{
field: 'process.hash.*',
operator: 'included',
type: 'match',
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
},
{
field: 'process.hash.*',
operator: 'included',
type: 'match',
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
},
{
field: 'process.hash.*',
operator: 'included',
type: 'match',
value: '741462ab431a22233c787baab9b653c7',
},
],
id: '1',
name: 'test',
os: 'windows',
},
],
page: 10,
per_page: 100,
total: 0,
},
});
});

it('should log unexpected error if one occurs', async () => {
exceptionsListClient.findExceptionListItem.mockImplementation(() => {
throw new Error('expected error');
Expand All @@ -138,24 +232,26 @@ describe('when invoking endpoint trusted apps route handlers', () => {

describe('when creating a trusted app', () => {
let routeHandler: RequestHandler<undefined, PostTrustedAppCreateRequest>;
const createNewTrustedAppBody = (): PostTrustedAppCreateRequest => ({
const createNewTrustedAppBody = (): {
-readonly [k in keyof PostTrustedAppCreateRequest]: PostTrustedAppCreateRequest[k];
} => ({
name: 'Some Anti-Virus App',
description: 'this one is ok',
os: 'windows',
entries: [
{
field: 'process.path',
field: 'process.path.text',
type: 'match',
operator: 'included',
value: 'c:/programs files/Anti-Virus',
},
],
});
const createPostRequest = () => {
const createPostRequest = (body?: PostTrustedAppCreateRequest) => {
return httpServerMock.createKibanaRequest<undefined, PostTrustedAppCreateRequest>({
path: TRUSTED_APPS_LIST_API,
method: 'post',
body: createNewTrustedAppBody(),
body: body ?? createNewTrustedAppBody(),
});
};

Expand Down Expand Up @@ -197,7 +293,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
description: 'this one is ok',
entries: [
{
field: 'process.path',
field: 'process.path.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
Expand All @@ -224,7 +320,7 @@ describe('when invoking endpoint trusted apps route handlers', () => {
description: 'this one is ok',
entries: [
{
field: 'process.path',
field: 'process.path.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
Expand All @@ -247,6 +343,134 @@ describe('when invoking endpoint trusted apps route handlers', () => {
expect(response.internalError).toHaveBeenCalled();
expect(endpointAppContext.logFactory.get('trusted_apps').error).toHaveBeenCalled();
});

it('should trim trusted app entry name', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.name = `\n ${newTrustedApp.name} \r\n`;
const request = createPostRequest(newTrustedApp);
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].name).toEqual(
'Some Anti-Virus App'
);
});

it('should trim condition entry values', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.entries.push({
field: 'process.path.text',
value: '\n some value \r\n ',
operator: 'included',
type: 'match',
});
const request = createPostRequest(newTrustedApp);
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
field: 'process.path.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
},
{
field: 'process.path.text',
value: 'some value',
operator: 'included',
type: 'match',
},
]);
});

it('should convert hash values to lowercase', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.entries.push({
field: 'process.hash.*',
value: '741462AB431A22233C787BAAB9B653C7',
operator: 'included',
type: 'match',
});
const request = createPostRequest(newTrustedApp);
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
field: 'process.path.text',
operator: 'included',
type: 'match',
value: 'c:/programs files/Anti-Virus',
},
{
field: 'process.hash.md5',
value: '741462ab431a22233c787baab9b653c7',
operator: 'included',
type: 'match',
},
]);
});

it('should detect md5 hash', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.entries = [
{
field: 'process.hash.*',
value: '741462ab431a22233c787baab9b653c7',
operator: 'included',
type: 'match',
},
];
const request = createPostRequest(newTrustedApp);
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
field: 'process.hash.md5',
value: '741462ab431a22233c787baab9b653c7',
operator: 'included',
type: 'match',
},
]);
});

it('should detect sha1 hash', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.entries = [
{
field: 'process.hash.*',
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
operator: 'included',
type: 'match',
},
];
const request = createPostRequest(newTrustedApp);
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
field: 'process.hash.sha1',
value: 'aedb279e378bed6c2db3c9dc9e12ba635e0b391c',
operator: 'included',
type: 'match',
},
]);
});

it('should detect sha256 hash', async () => {
const newTrustedApp = createNewTrustedAppBody();
newTrustedApp.entries = [
{
field: 'process.hash.*',
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
operator: 'included',
type: 'match',
},
];
const request = createPostRequest(newTrustedApp);
await routeHandler(context, request, response);
expect(exceptionsListClient.createExceptionListItem.mock.calls[0][0].entries).toEqual([
{
field: 'process.hash.sha256',
value: 'a4370c0cf81686c0b696fa6261c9d3e0d810ae704ab8301839dffd5d5112f476',
operator: 'included',
type: 'match',
},
]);
});
});

describe('when deleting a trusted app', () => {
Expand Down
Loading

0 comments on commit 8a5a592

Please sign in to comment.