Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[SECURITY_SOLUTION][ENDPOINT] Create Trusted Apps API changes to process user input #78079

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];
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what does this syntax mean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It makes the object "own keys" mutable (you can mutate them). Its short of writing a Mutable<> generic interface (to undo what our Immutable<> generic does :)

} => ({
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