[Security Solution][Endpoint] Remove hapi/boom requests (#117654)

* remove unused code

fixes elastic/security-team/issues/1988

* remove redundant literal

* custom error instead of Boom

fixes elastic/security-team/issues/1988

* remove legacy boom check

review comments

* commit using ashokaditya@elastic.co

* move custom error to a common folder

review suggestion

* use the logger to log the error

review changes

* fix lint

* more generic custom error that allows for custom statusCodes

* remove hapi/boom from transformError

review suggestions

* Revert "remove hapi/boom from transformError"

This reverts commit 90e4b50445.

* review changes

Co-authored-by: Kibana Machine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Ashokaditya 2021-11-22 16:02:04 +01:00 committed by GitHub
parent f1d1cb5505
commit 60ae6f9f05
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 44 additions and 140 deletions

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { TypeOf } from '@kbn/config-schema';
import {
IKibanaResponse,
@ -25,12 +23,8 @@ import {
} from '../../../../common/endpoint/types';
import type { SecuritySolutionRequestHandlerContext } from '../../../types';
import {
getESQueryHostMetadataByID,
getPagingProperties,
kibanaRequestToMetadataListESQuery,
} from './query_builders';
import { Agent, PackagePolicy } from '../../../../../fleet/common/types/models';
import { getPagingProperties, kibanaRequestToMetadataListESQuery } from './query_builders';
import { PackagePolicy } from '../../../../../fleet/common/types/models';
import { AgentNotFoundError } from '../../../../../fleet/server';
import { EndpointAppContext, HostListQueryResult } from '../../types';
import {
@ -42,13 +36,11 @@ import { findAllUnenrolledAgentIds } from './support/unenroll';
import { getAllEndpointPackagePolicies } from './support/endpoint_package_policies';
import { findAgentIdsByStatus } from './support/agent_status';
import { EndpointAppContextService } from '../../endpoint_app_context_services';
import { catchAndWrapError, fleetAgentStatusToEndpointHostStatus } from '../../utils';
import {
queryResponseToHostListResult,
queryResponseToHostResult,
} from './support/query_strategies';
import { fleetAgentStatusToEndpointHostStatus } from '../../utils';
import { queryResponseToHostListResult } from './support/query_strategies';
import { EndpointError, NotFoundError } from '../../errors';
import { EndpointHostUnEnrolledError } from '../../services/metadata';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
export interface MetadataRequestContext {
esClient?: IScopedClusterClient;
@ -67,6 +59,15 @@ const errorHandler = <E extends Error>(
res: KibanaResponseFactory,
error: E
): IKibanaResponse => {
logger.error(error);
if (error instanceof CustomHttpRequestError) {
return res.customError({
statusCode: error.statusCode,
body: error,
});
}
if (error instanceof NotFoundError) {
return res.notFound({ body: error });
}
@ -75,16 +76,6 @@ const errorHandler = <E extends Error>(
return res.badRequest({ body: error });
}
// legacy check for Boom errors. for the errors around non-standard error properties
// @ts-expect-error TS2339
const boomStatusCode = error.isBoom && error?.output?.statusCode;
if (boomStatusCode) {
return res.customError({
statusCode: boomStatusCode,
body: error,
});
}
// Kibana CORE will take care of `500` errors when the handler `throw`'s, including logging the error
throw error;
};
@ -269,107 +260,6 @@ export const getMetadataRequestHandler = function (
};
};
export async function getHostMetaData(
metadataRequestContext: MetadataRequestContext,
id: string
): Promise<HostMetadata | undefined> {
if (
!metadataRequestContext.esClient &&
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
) {
throw Boom.badRequest('esClient not found');
}
if (
!metadataRequestContext.savedObjectsClient &&
!metadataRequestContext.requestHandlerContext?.core.savedObjects
) {
throw Boom.badRequest('savedObjectsClient not found');
}
const esClient = (metadataRequestContext?.esClient ??
metadataRequestContext.requestHandlerContext?.core.elasticsearch
.client) as IScopedClusterClient;
const query = getESQueryHostMetadataByID(id);
const response = await esClient.asCurrentUser
.search<HostMetadata>(query)
.catch(catchAndWrapError);
const hostResult = queryResponseToHostResult(response.body);
const hostMetadata = hostResult.result;
if (!hostMetadata) {
return undefined;
}
return hostMetadata;
}
export async function getHostData(
metadataRequestContext: MetadataRequestContext,
id: string
): Promise<HostInfo | undefined> {
if (!metadataRequestContext.savedObjectsClient) {
throw Boom.badRequest('savedObjectsClient not found');
}
if (
!metadataRequestContext.esClient &&
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
) {
throw Boom.badRequest('esClient not found');
}
const hostMetadata = await getHostMetaData(metadataRequestContext, id);
if (!hostMetadata) {
return undefined;
}
const agent = await findAgent(metadataRequestContext, hostMetadata);
if (agent && !agent.active) {
throw Boom.badRequest('the requested endpoint is unenrolled');
}
const metadata = await enrichHostMetadata(hostMetadata, metadataRequestContext);
return metadata;
}
async function findAgent(
metadataRequestContext: MetadataRequestContext,
hostMetadata: HostMetadata
): Promise<Agent | undefined> {
try {
if (
!metadataRequestContext.esClient &&
!metadataRequestContext.requestHandlerContext?.core.elasticsearch.client
) {
throw new Error('esClient not found');
}
const esClient = (metadataRequestContext?.esClient ??
metadataRequestContext.requestHandlerContext?.core.elasticsearch
.client) as IScopedClusterClient;
return await metadataRequestContext.endpointAppContextService
?.getAgentService()
?.getAgent(esClient.asCurrentUser, hostMetadata.elastic.agent.id);
} catch (e) {
if (e instanceof AgentNotFoundError) {
metadataRequestContext.logger.warn(
`agent with id ${hostMetadata.elastic.agent.id} not found`
);
return undefined;
} else {
throw e;
}
}
}
export async function mapToHostResultList(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
queryParams: Record<string, any>,

View file

@ -125,7 +125,7 @@ export const createRulesBulkRoute = (
} catch (err) {
return transformBulkError(
internalRule.params.ruleId,
err as Error & { statusCode?: number | undefined }
err as Error & { statusCode?: number }
);
}
})

View file

@ -5,8 +5,6 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { SavedObjectsFindResponse } from 'kibana/server';
import { rulesClientMock } from '../../../../../alerting/server/mocks';
@ -26,6 +24,7 @@ import { getAlertMock } from './__mocks__/request_responses';
import { AlertExecutionStatusErrorReasons } from '../../../../../alerting/common';
import { getQueryRuleParams } from '../schemas/rule_schemas.mock';
import { RuleExecutionStatus } from '../../../../common/detection_engine/schemas/common/schemas';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
let rulesClient: ReturnType<typeof rulesClientMock.create>;
@ -34,17 +33,17 @@ describe.each([
['RAC', true],
])('utils - %s', (_, isRuleRegistryEnabled) => {
describe('transformBulkError', () => {
test('returns transformed object if it is a boom object', () => {
const boom = new Boom.Boom('some boom message', { statusCode: 400 });
const transformed = transformBulkError('rule-1', boom);
test('returns transformed object if it is a custom error object', () => {
const customError = new CustomHttpRequestError('some custom error message', 400);
const transformed = transformBulkError('rule-1', customError);
const expected: BulkError = {
rule_id: 'rule-1',
error: { message: 'some boom message', status_code: 400 },
error: { message: 'some custom error message', status_code: 400 },
};
expect(transformed).toEqual(expected);
});
test('returns a normal error if it is some non boom object that has a statusCode', () => {
test('returns a normal error if it is some non custom error that has a statusCode', () => {
const error: Error & { statusCode?: number } = {
statusCode: 403,
name: 'some name',
@ -71,7 +70,7 @@ describe.each([
expect(transformed).toEqual(expected);
});
test('it detects a BadRequestError and returns a Boom status of 400', () => {
test('it detects a BadRequestError and returns an error status of 400', () => {
const error: BadRequestError = new BadRequestError('I have a type error');
const transformed = transformBulkError('rule-1', error);
const expected: BulkError = {

View file

@ -5,7 +5,6 @@
* 2.0.
*/
import Boom from '@hapi/boom';
import { has, snakeCase } from 'lodash/fp';
import { BadRequestError } from '@kbn/securitysolution-es-utils';
import { SanitizedAlert } from '../../../../../alerting/common';
@ -19,6 +18,7 @@ import { RulesClient } from '../../../../../alerting/server';
import { RuleStatusResponse, IRuleStatusSOAttributes } from '../rules/types';
import { RuleParams } from '../schemas/rule_schemas';
import { CustomHttpRequestError } from '../../../utils/custom_http_request_error';
export interface OutputError {
message: string;
@ -104,10 +104,10 @@ export const transformBulkError = (
ruleId: string,
err: Error & { statusCode?: number }
): BulkError => {
if (Boom.isBoom(err)) {
if (err instanceof CustomHttpRequestError) {
return createBulkErrorObject({
ruleId,
statusCode: err.output.statusCode,
statusCode: err.statusCode ?? 400,
message: err.message,
});
} else if (err instanceof BadRequestError) {
@ -254,7 +254,7 @@ export const getFailingRules = async (
};
}, {});
} catch (exc) {
if (Boom.isBoom(exc)) {
if (exc instanceof CustomHttpRequestError) {
throw exc;
}
throw new Error(`Failed to get executionStatus with RulesClient: ${(exc as Error).message}`);

View file

@ -4,7 +4,6 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import Boom from '@hapi/boom';
import { pipe } from 'fp-ts/lib/pipeable';
import { fold } from 'fp-ts/lib/Either';
@ -19,13 +18,14 @@ import { SetupPlugins } from '../../../../../plugin';
import { buildSiemResponse } from '../../../../detection_engine/routes/utils';
import { CustomHttpRequestError } from '../../../../../utils/custom_http_request_error';
import { buildFrameworkRequest, escapeHatch, throwErrors } from '../../../utils/common';
import { getAllTimeline } from '../../../saved_object/timelines';
import { getTimelinesQuerySchema } from '../../../schemas/timelines';
export const getTimelinesRoute = (
router: SecuritySolutionPluginRouter,
config: ConfigType,
_config: ConfigType,
security: SetupPlugins['security']
) => {
router.get(
@ -39,11 +39,12 @@ export const getTimelinesRoute = (
},
},
async (context, request, response) => {
const customHttpRequestError = (message: string) => new CustomHttpRequestError(message, 400);
try {
const frameworkRequest = await buildFrameworkRequest(context, security, request);
const queryParams = pipe(
getTimelinesQuerySchema.decode(request.query),
fold(throwErrors(Boom.badRequest), identity)
fold(throwErrors(customHttpRequestError), identity)
);
const onlyUserFavorite = queryParams?.only_user_favorite === 'true' ? true : false;
const pageSize = queryParams?.page_size ? parseInt(queryParams.page_size, 10) : null;

View file

@ -0,0 +1,14 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
export class CustomHttpRequestError extends Error {
constructor(message: string, public readonly statusCode: number = 500) {
super(message);
// For debugging - capture name of subclasses
this.name = this.constructor.name;
this.message = message;
}
}