Skip to content

Commit

Permalink
[SECURITY SOLUTION][ENDPOINT] Trusted Apps API to retrieve item summa…
Browse files Browse the repository at this point in the history
…ry by OS (#85870)

* API to return trusted apps summary
  • Loading branch information
paul-tavares authored Dec 15, 2020
1 parent 1c1e498 commit 23cd267
Show file tree
Hide file tree
Showing 8 changed files with 203 additions and 1 deletion.
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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';
export const TRUSTED_APPS_DELETE_API = '/api/endpoint/trusted_apps/{id}';
export const TRUSTED_APPS_SUMMARY_API = '/api/endpoint/trusted_apps/summary';

export const BASE_POLICY_RESPONSE_ROUTE = `/api/endpoint/policy_response`;
export const BASE_POLICY_ROUTE = `/api/endpoint/policy`;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ export interface PostTrustedAppCreateResponse {
data: TrustedApp;
}

export interface GetTrustedAppsSummaryResponse {
total: number;
windows: number;
macos: number;
linux: number;
}

export enum ConditionEntryField {
HASH = 'process.hash.*',
PATH = 'process.executable.caseless',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
getTrustedAppsCreateRouteHandler,
getTrustedAppsDeleteRouteHandler,
getTrustedAppsListRouteHandler,
getTrustedAppsSummaryRouteHandler,
} from './handlers';

const exceptionsListClient = listMock.getExceptionListClient() as jest.Mocked<ExceptionListClient>;
Expand Down Expand Up @@ -230,4 +231,70 @@ describe('handlers', () => {
expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error);
});
});

describe('getTrustedAppsSummaryHandler', () => {
const getTrustedAppsSummaryHandler = getTrustedAppsSummaryRouteHandler(appContextMock);

it('should return ok with list when no errors', async () => {
const mockResponse = httpServerMock.createResponseFactory();

exceptionsListClient.findExceptionListItem.mockResolvedValue({
data: [
// Linux === 5
...Array.from({ length: 5 }, () => {
return {
...EXCEPTION_LIST_ITEM,
};
}),
// macos === 3
...Array.from({ length: 3 }, () => {
return {
...EXCEPTION_LIST_ITEM,
os_types: ['macos'] as ExceptionListItemSchema['os_types'],
};
}),

// windows === 15
...Array.from({ length: 15 }, () => {
return {
...EXCEPTION_LIST_ITEM,
os_types: ['windows'] as ExceptionListItemSchema['os_types'],
};
}),
],
page: 1,
per_page: 100,
total: 23,
});

await getTrustedAppsSummaryHandler(
createHandlerContextMock(),
httpServerMock.createKibanaRequest(),
mockResponse
);

assertResponse(mockResponse, 'ok', {
linux: 5,
macos: 3,
windows: 15,
total: 23,
});
});

it('should return internalError when errors happen', async () => {
const mockResponse = httpServerMock.createResponseFactory();
const error = new Error('Something went wrong');

exceptionsListClient.findExceptionListItem.mockRejectedValue(error);

await getTrustedAppsSummaryHandler(
createHandlerContextMock(),
httpServerMock.createKibanaRequest(),
mockResponse
);

assertResponse(mockResponse, 'internalError', error);
expect(appContextMock.logFactory.get('trusted_apps').error).toHaveBeenCalledWith(error);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
createTrustedApp,
deleteTrustedApp,
getTrustedAppsList,
getTrustedAppsSummary,
MissingTrustedAppException,
} from './service';

Expand Down Expand Up @@ -86,3 +87,20 @@ export const getTrustedAppsCreateRouteHandler = (
}
};
};

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

return async (context, req, res) => {
try {
return res.ok({
body: await getTrustedAppsSummary(exceptionListClientFromContext(context)),
});
} catch (error) {
logger.error(error);
return res.internalError({ body: error });
}
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import {
TRUSTED_APPS_CREATE_API,
TRUSTED_APPS_DELETE_API,
TRUSTED_APPS_LIST_API,
TRUSTED_APPS_SUMMARY_API,
} from '../../../../common/endpoint/constants';
import {
getTrustedAppsCreateRouteHandler,
getTrustedAppsDeleteRouteHandler,
getTrustedAppsListRouteHandler,
getTrustedAppsSummaryRouteHandler,
} from './handlers';
import { EndpointAppContext } from '../../types';

Expand Down Expand Up @@ -55,4 +57,14 @@ export const registerTrustedAppsRoutes = (
},
getTrustedAppsCreateRouteHandler(endpointAppContext)
);

// SUMMARY
router.get(
{
path: TRUSTED_APPS_SUMMARY_API,
validate: false,
options: { authRequired: true },
},
getTrustedAppsSummaryRouteHandler(endpointAppContext)
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ export const exceptionListItemToTrustedApp = (
exceptionListItem: ExceptionListItemSchema
): TrustedApp => {
if (exceptionListItem.os_types[0]) {
const os = OS_TYPE_TO_OPERATING_SYSTEM[exceptionListItem.os_types[0]];
const os = osFromExceptionItem(exceptionListItem);
const grouped = entriesToConditionEntriesMap(exceptionListItem.entries);

return {
Expand Down Expand Up @@ -121,6 +121,12 @@ export const exceptionListItemToTrustedApp = (
}
};

export const osFromExceptionItem = (
exceptionListItem: ExceptionListItemSchema
): TrustedApp['os'] => {
return OS_TYPE_TO_OPERATING_SYSTEM[exceptionListItem.os_types[0]];
};

const hashType = (hash: string): 'md5' | 'sha256' | 'sha1' | undefined => {
switch (hash.length) {
case 32:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
createTrustedApp,
deleteTrustedApp,
getTrustedAppsList,
getTrustedAppsSummary,
MissingTrustedAppException,
} from './service';

Expand Down Expand Up @@ -120,4 +121,52 @@ describe('service', () => {
expect(exceptionsListClient.createTrustedAppsList).toHaveBeenCalled();
});
});

describe('getTrustedAppsSummary', () => {
beforeEach(() => {
exceptionsListClient.findExceptionListItem.mockImplementation(async ({ page }) => {
let data: ExceptionListItemSchema[] = [];

// linux ++ windows entries
if (page === 1) {
data = [
...Array.from<void, ExceptionListItemSchema>({ length: 45 }, () => ({
...EXCEPTION_LIST_ITEM,
os_types: ['linux'],
})),
...Array.from({ length: 55 }, () => ({
...EXCEPTION_LIST_ITEM,
os_types: ['windows'] as ExceptionListItemSchema['os_types'],
})),
];
}

// macos entries
if (page === 2) {
data = [
...Array.from({ length: 30 }, () => ({
...EXCEPTION_LIST_ITEM,
os_types: ['macos'] as ExceptionListItemSchema['os_types'],
})),
];
}

return {
data,
page: page || 1,
total: 130,
per_page: 100,
};
});
});

it('should return summary of trusted app items', async () => {
expect(await getTrustedAppsSummary(exceptionsListClient)).toEqual({
linux: 45,
windows: 55,
macos: 30,
total: 130,
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { ENDPOINT_TRUSTED_APPS_LIST_ID } from '../../../../../lists/common/const
import {
DeleteTrustedAppsRequestParams,
GetTrustedAppsListRequest,
GetTrustedAppsSummaryResponse,
GetTrustedListAppsResponse,
PostTrustedAppCreateRequest,
PostTrustedAppCreateResponse,
Expand All @@ -18,6 +19,7 @@ import {
import {
exceptionListItemToTrustedApp,
newTrustedAppToCreateExceptionListItemOptions,
osFromExceptionItem,
} from './mapping';

export class MissingTrustedAppException {
Expand Down Expand Up @@ -77,3 +79,43 @@ export const createTrustedApp = async (

return { data: exceptionListItemToTrustedApp(createdTrustedAppExceptionItem) };
};

export const getTrustedAppsSummary = async (
exceptionsListClient: ExceptionListClient
): Promise<GetTrustedAppsSummaryResponse> => {
// Ensure list is created if it does not exist
await exceptionsListClient.createTrustedAppsList();

const summary = {
linux: 0,
windows: 0,
macos: 0,
total: 0,
};
const perPage = 100;
let paging = true;
let page = 1;

while (paging) {
const { data, total } = (await exceptionsListClient.findExceptionListItem({
listId: ENDPOINT_TRUSTED_APPS_LIST_ID,
page,
perPage,
filter: undefined,
namespaceType: 'agnostic',
sortField: undefined,
sortOrder: undefined,
}))!;

summary.total = total;

for (const item of data) {
summary[osFromExceptionItem(item)]++;
}

paging = (page - 1) * perPage + data.length < total;
page++;
}

return summary;
};

0 comments on commit 23cd267

Please sign in to comment.