mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 01:38:56 -04:00
[Cloud Security] convert stats api router to be versioned
This commit is contained in:
parent
a5ef10ec5e
commit
56e9cebe21
7 changed files with 110 additions and 85 deletions
|
@ -21,9 +21,9 @@ import type {
|
|||
KibanaResponseFactory,
|
||||
} from '@kbn/core-http-server';
|
||||
import { CoreKibanaRequest } from '@kbn/core-http-router-server-internal';
|
||||
import { createVersionedRouterMock } from './versioned_router.mock';
|
||||
import { createVersionedRouterMock, type MockedVersionedRouter } from './versioned_router.mock';
|
||||
|
||||
export type RouterMock = jest.Mocked<IRouter<any>>;
|
||||
export type RouterMock = jest.Mocked<IRouter<any>> & { versioned: MockedVersionedRouter };
|
||||
|
||||
function createRouterMock({ routerPath = '' }: { routerPath?: string } = {}): RouterMock {
|
||||
return {
|
||||
|
|
|
@ -13,7 +13,9 @@ const createMockVersionedRoute = (): VersionedRoute => {
|
|||
return api;
|
||||
};
|
||||
|
||||
export const createVersionedRouterMock = (): jest.Mocked<VersionedRouter<any>> => ({
|
||||
export type MockedVersionedRouter = jest.Mocked<VersionedRouter<any>>;
|
||||
|
||||
export const createVersionedRouterMock = (): MockedVersionedRouter => ({
|
||||
delete: jest.fn((_) => createMockVersionedRoute()),
|
||||
get: jest.fn((_) => createMockVersionedRoute()),
|
||||
patch: jest.fn((_) => createMockVersionedRoute()),
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { CSPM_POLICY_TEMPLATE, KSPM_POLICY_TEMPLATE } from '../constants';
|
||||
|
||||
// this pages follows versioning interface strategy https://docs.elastic.dev/kibana-dev-docs/versioning-interfaces
|
||||
|
||||
export const getComplianceDashboardSchema = schema.object({
|
||||
policy_template: schema.oneOf([
|
||||
schema.literal(CSPM_POLICY_TEMPLATE),
|
||||
schema.literal(KSPM_POLICY_TEMPLATE),
|
||||
]),
|
||||
});
|
|
@ -11,6 +11,7 @@ import { SUPPORTED_CLOUDBEAT_INPUTS, SUPPORTED_POLICY_TEMPLATES } from './consta
|
|||
import { CspRuleTemplateMetadata } from './schemas/csp_rule_template_metadata';
|
||||
import { CspRuleTemplate } from './schemas';
|
||||
import { findCspRuleTemplateRequest } from './schemas/csp_rule_template_api/get_csp_rule_template';
|
||||
import { getComplianceDashboardSchema } from './schemas/stats';
|
||||
|
||||
export type Evaluation = 'passed' | 'failed' | 'NA';
|
||||
|
||||
|
@ -119,6 +120,8 @@ export interface BenchmarkResponse {
|
|||
|
||||
export type GetCspRuleTemplateRequest = TypeOf<typeof findCspRuleTemplateRequest>;
|
||||
|
||||
export type GetComplianceDashboardRequest = TypeOf<typeof getComplianceDashboardSchema>;
|
||||
|
||||
export interface GetCspRuleTemplateResponse {
|
||||
items: CspRuleTemplate[];
|
||||
total: number;
|
||||
|
|
|
@ -28,7 +28,7 @@ export const useCspmStatsApi = (
|
|||
const { http } = useKibana().services;
|
||||
return useQuery(
|
||||
getCspmStatsKey,
|
||||
() => http.get<ComplianceDashboardData>(getStatsRoute(CSPM_POLICY_TEMPLATE)),
|
||||
() => http.get<ComplianceDashboardData>(getStatsRoute(CSPM_POLICY_TEMPLATE), { version: '1' }),
|
||||
options
|
||||
);
|
||||
};
|
||||
|
@ -39,7 +39,7 @@ export const useKspmStatsApi = (
|
|||
const { http } = useKibana().services;
|
||||
return useQuery(
|
||||
getKspmStatsKey,
|
||||
() => http.get<ComplianceDashboardData>(getStatsRoute(KSPM_POLICY_TEMPLATE)),
|
||||
() => http.get<ComplianceDashboardData>(getStatsRoute(KSPM_POLICY_TEMPLATE), { version: '1' }),
|
||||
options
|
||||
);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,10 @@ describe('compliance dashboard permissions API', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
defineGetComplianceDashboardRoute(router);
|
||||
const [_, handler] = router.get.mock.calls[0];
|
||||
|
||||
const versionedRouter = router.versioned.get.mock.results[0].value;
|
||||
|
||||
const handler = versionedRouter.addVersion.mock.calls[0][1];
|
||||
|
||||
const mockContext = createCspRequestHandlerContextMock();
|
||||
const mockResponse = httpServerMock.createResponseFactory();
|
||||
|
@ -33,7 +36,10 @@ describe('compliance dashboard permissions API', () => {
|
|||
const router = httpServiceMock.createRouter();
|
||||
|
||||
defineGetComplianceDashboardRoute(router);
|
||||
const [_, handler] = router.get.mock.calls[0];
|
||||
|
||||
const versionedRouter = router.versioned.get.mock.results[0].value;
|
||||
|
||||
const handler = versionedRouter.addVersion.mock.calls[0][1];
|
||||
|
||||
const mockContext = createCspRequestHandlerContextMock();
|
||||
const mockResponse = httpServerMock.createResponseFactory();
|
||||
|
|
|
@ -7,16 +7,15 @@
|
|||
|
||||
import { transformError } from '@kbn/securitysolution-es-utils';
|
||||
import type { QueryDslQueryContainer } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { schema } from '@kbn/config-schema';
|
||||
import { MappingRuntimeFields } from '@elastic/elasticsearch/lib/api/types';
|
||||
import { getComplianceDashboardSchema } from '../../../common/schemas/stats';
|
||||
import { getSafePostureTypeRuntimeMapping } from '../../../common/runtime_mappings/get_safe_posture_type_runtime_mapping';
|
||||
import type { PosturePolicyTemplate, ComplianceDashboardData } from '../../../common/types';
|
||||
import {
|
||||
CSPM_POLICY_TEMPLATE,
|
||||
KSPM_POLICY_TEMPLATE,
|
||||
LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
STATS_ROUTE_PATH,
|
||||
} from '../../../common/constants';
|
||||
import type {
|
||||
PosturePolicyTemplate,
|
||||
ComplianceDashboardData,
|
||||
GetComplianceDashboardRequest,
|
||||
} from '../../../common/types';
|
||||
import { LATEST_FINDINGS_INDEX_DEFAULT_NS, STATS_ROUTE_PATH } from '../../../common/constants';
|
||||
import { getGroupedFindingsEvaluation } from './get_grouped_findings_evaluation';
|
||||
import { ClusterWithoutTrend, getClusters } from './get_clusters';
|
||||
import { getStats } from './get_stats';
|
||||
|
@ -40,82 +39,79 @@ const getClustersTrends = (clustersWithoutTrends: ClusterWithoutTrend[], trends:
|
|||
const getSummaryTrend = (trends: Trends) =>
|
||||
trends.map(({ timestamp, summary }) => ({ timestamp, ...summary }));
|
||||
|
||||
const queryParamsSchema = {
|
||||
params: schema.object({
|
||||
policy_template: schema.oneOf([
|
||||
schema.literal(CSPM_POLICY_TEMPLATE),
|
||||
schema.literal(KSPM_POLICY_TEMPLATE),
|
||||
]),
|
||||
}),
|
||||
};
|
||||
|
||||
export const defineGetComplianceDashboardRoute = (router: CspRouter): void =>
|
||||
router.get(
|
||||
{
|
||||
export const defineGetComplianceDashboardRoute = (router: CspRouter) =>
|
||||
router.versioned
|
||||
.get({
|
||||
access: 'internal',
|
||||
path: STATS_ROUTE_PATH,
|
||||
validate: queryParamsSchema,
|
||||
options: {
|
||||
tags: ['access:cloud-security-posture-read'],
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const cspContext = await context.csp;
|
||||
|
||||
try {
|
||||
const esClient = cspContext.esClient.asCurrentUser;
|
||||
|
||||
const { id: pitId } = await esClient.openPointInTime({
|
||||
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
keep_alive: '30s',
|
||||
});
|
||||
|
||||
const policyTemplate = request.params.policy_template as PosturePolicyTemplate;
|
||||
|
||||
// runtime mappings create the `safe_posture_type` field, which equals to `kspm` or `cspm` based on the value and existence of the `posture_type` field which was introduced at 8.7
|
||||
// the `query` is then being passed to our getter functions to filter per posture type even for older findings before 8.7
|
||||
const runtimeMappings: MappingRuntimeFields = getSafePostureTypeRuntimeMapping();
|
||||
const query: QueryDslQueryContainer = {
|
||||
bool: {
|
||||
filter: [{ term: { safe_posture_type: policyTemplate } }],
|
||||
})
|
||||
.addVersion(
|
||||
{
|
||||
version: '1',
|
||||
validate: {
|
||||
request: {
|
||||
params: getComplianceDashboardSchema,
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
async (context, request, response) => {
|
||||
const cspContext = await context.csp;
|
||||
|
||||
const [stats, groupedFindingsEvaluation, clustersWithoutTrends, trends] = await Promise.all(
|
||||
[
|
||||
getStats(esClient, query, pitId, runtimeMappings),
|
||||
getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings),
|
||||
getClusters(esClient, query, pitId, runtimeMappings),
|
||||
getTrends(esClient, policyTemplate),
|
||||
]
|
||||
);
|
||||
try {
|
||||
const esClient = cspContext.esClient.asCurrentUser;
|
||||
|
||||
// Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive
|
||||
// ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request
|
||||
esClient.closePointInTime({ id: pitId }).catch((err) => {
|
||||
cspContext.logger.warn(`Could not close PIT for stats endpoint: ${err}`);
|
||||
});
|
||||
const { id: pitId } = await esClient.openPointInTime({
|
||||
index: LATEST_FINDINGS_INDEX_DEFAULT_NS,
|
||||
keep_alive: '30s',
|
||||
});
|
||||
|
||||
const clusters = getClustersTrends(clustersWithoutTrends, trends);
|
||||
const trend = getSummaryTrend(trends);
|
||||
const params: GetComplianceDashboardRequest = request.params;
|
||||
const policyTemplate = params.policy_template as PosturePolicyTemplate;
|
||||
|
||||
const body: ComplianceDashboardData = {
|
||||
stats,
|
||||
groupedFindingsEvaluation,
|
||||
clusters,
|
||||
trend,
|
||||
};
|
||||
// runtime mappings create the `safe_posture_type` field, which equals to `kspm` or `cspm` based on the value and existence of the `posture_type` field which was introduced at 8.7
|
||||
// the `query` is then being passed to our getter functions to filter per posture type even for older findings before 8.7
|
||||
const runtimeMappings: MappingRuntimeFields = getSafePostureTypeRuntimeMapping();
|
||||
const query: QueryDslQueryContainer = {
|
||||
bool: {
|
||||
filter: [{ term: { safe_posture_type: policyTemplate } }],
|
||||
},
|
||||
};
|
||||
|
||||
return response.ok({
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
cspContext.logger.error(`Error while fetching CSP stats: ${err}`);
|
||||
const [stats, groupedFindingsEvaluation, clustersWithoutTrends, trends] =
|
||||
await Promise.all([
|
||||
getStats(esClient, query, pitId, runtimeMappings),
|
||||
getGroupedFindingsEvaluation(esClient, query, pitId, runtimeMappings),
|
||||
getClusters(esClient, query, pitId, runtimeMappings),
|
||||
getTrends(esClient, policyTemplate),
|
||||
]);
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
// Try closing the PIT, if it fails we can safely ignore the error since it closes itself after the keep alive
|
||||
// ends. Not waiting on the promise returned from the `closePointInTime` call to avoid delaying the request
|
||||
esClient.closePointInTime({ id: pitId }).catch((err) => {
|
||||
cspContext.logger.warn(`Could not close PIT for stats endpoint: ${err}`);
|
||||
});
|
||||
|
||||
const clusters = getClustersTrends(clustersWithoutTrends, trends);
|
||||
const trend = getSummaryTrend(trends);
|
||||
|
||||
const body: ComplianceDashboardData = {
|
||||
stats,
|
||||
groupedFindingsEvaluation,
|
||||
clusters,
|
||||
trend,
|
||||
};
|
||||
|
||||
return response.ok({
|
||||
body,
|
||||
});
|
||||
} catch (err) {
|
||||
const error = transformError(err);
|
||||
cspContext.logger.error(`Error while fetching CSP stats: ${err}`);
|
||||
|
||||
return response.customError({
|
||||
body: { message: error.message },
|
||||
statusCode: error.statusCode,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue