mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 17:59:23 -04:00
[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:
parent
f1d1cb5505
commit
60ae6f9f05
6 changed files with 44 additions and 140 deletions
|
@ -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>,
|
||||
|
|
|
@ -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 }
|
||||
);
|
||||
}
|
||||
})
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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}`);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue