Cloud defend versioned api (#159171)

## Summary

### Issues: 
- https://github.com/elastic/kibana/issues/158688

### Fixes
- API routes now versioned
- types put under a latest/v1 export paradigm

### Checklist

Delete any items that are not applicable to this PR.

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Karl Godard 2023-06-13 09:27:26 -07:00 committed by GitHub
parent d72524a72d
commit d4e92c06a0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 164 additions and 116 deletions

View file

@ -13,7 +13,11 @@ export const INPUT_CONTROL = 'cloud_defend/control';
export const ALERTS_DATASET = 'cloud_defend.alerts';
export const ALERTS_INDEX_PATTERN = 'cloud_defend.alerts*';
export const CURRENT_API_VERSION = '1';
export const POLICIES_ROUTE_PATH = '/internal/cloud_defend/policies';
export const STATUS_ROUTE_PATH = '/internal/cloud_defend/status';
export const CLOUD_DEFEND_FLEET_PACKAGE_KUERY = `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${INTEGRATION_PACKAGE_NAME}`;
export const DEFAULT_POLICIES_PER_PAGE = 20;
export const POLICIES_PACKAGE_POLICY_PREFIX = 'package_policy.';

View file

@ -0,0 +1,21 @@
/*
* 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 type {
IndexDetails,
IndexStatus,
CloudDefendSetupStatus,
CloudDefendStatusCode,
AgentPolicyStatus,
CloudDefendPolicy,
PoliciesQueryParams,
} from './latest';
export { policiesQueryParamsSchema } from './latest';
import * as v1 from './v1';
import * as schemaV1 from './schemas/v1';
export { v1, schemaV1 };

View file

@ -0,0 +1,9 @@
/*
* 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 * from './v1';
export { policiesQueryParamsSchema } from './schemas/v1';
export type { PoliciesQueryParams } from './schemas/v1';

View file

@ -5,9 +5,8 @@
* 2.0.
*/
import { type TypeOf, schema } from '@kbn/config-schema';
import { DEFAULT_POLICIES_PER_PAGE } from '../constants';
export const DEFAULT_POLICIES_PER_PAGE = 20;
export const POLICIES_PACKAGE_POLICY_PREFIX = 'package_policy.';
export const policiesQueryParamsSchema = schema.object({
/**
* The page of objects to return

View file

@ -7,8 +7,8 @@
import { useQuery } from '@tanstack/react-query';
import { useKibana } from '../hooks/use_kibana';
import { CloudDefendSetupStatus } from '../../../common/types';
import { STATUS_ROUTE_PATH } from '../../../common/constants';
import { CloudDefendSetupStatus } from '../../../common';
import { CURRENT_API_VERSION, STATUS_ROUTE_PATH } from '../../../common/constants';
const getCloudDefendSetupStatusQueryKey = 'cloud_defend_status_key';
@ -16,6 +16,6 @@ export const useCloudDefendSetupStatusApi = () => {
const { http } = useKibana().services;
return useQuery<CloudDefendSetupStatus, unknown, CloudDefendSetupStatus>(
[getCloudDefendSetupStatusQueryKey],
() => http.get<CloudDefendSetupStatus>(STATUS_ROUTE_PATH)
() => http.get<CloudDefendSetupStatus>(STATUS_ROUTE_PATH, { version: CURRENT_API_VERSION })
);
};

View file

@ -17,7 +17,7 @@ import React from 'react';
import { pagePathGetters } from '@kbn/fleet-plugin/public';
import { i18n } from '@kbn/i18n';
import { TimestampTableCell } from '../timestamp_table_cell';
import type { CloudDefendPolicy } from '../../../common/types';
import type { CloudDefendPolicy } from '../../../common';
import { useKibana } from '../../common/hooks/use_kibana';
import * as TEST_SUBJ from '../../pages/policies/test_subjects';

View file

@ -7,10 +7,10 @@
import { useQuery } from '@tanstack/react-query';
import type { ListResult } from '@kbn/fleet-plugin/common';
import { POLICIES_ROUTE_PATH } from '../../../common/constants';
import type { PoliciesQueryParams } from '../../../common/schemas/policy';
import { CURRENT_API_VERSION, POLICIES_ROUTE_PATH } from '../../../common/constants';
import type { PoliciesQueryParams } from '../../../common';
import { useKibana } from '../../common/hooks/use_kibana';
import type { CloudDefendPolicy } from '../../../common/types';
import type { CloudDefendPolicy } from '../../../common';
const QUERY_KEY = 'cloud_defend_policies';
@ -40,7 +40,11 @@ export const useCloudDefendPolicies = ({
return useQuery(
[QUERY_KEY, query],
() => http.get<ListResult<CloudDefendPolicy>>(POLICIES_ROUTE_PATH, { query }),
() =>
http.get<ListResult<CloudDefendPolicy>>(POLICIES_ROUTE_PATH, {
version: CURRENT_API_VERSION,
query,
}),
{ keepPreviousData: true }
);
};

View file

@ -8,7 +8,7 @@
/* eslint-disable @typescript-eslint/naming-convention */
import Chance from 'chance';
import type { CloudDefendPolicy } from '../../../common/types';
import type { CloudDefendPolicy } from '../../../common';
type CreateCloudDefendIntegrationFixtureInput = {
chance?: Chance.Chance;

View file

@ -6,7 +6,7 @@
*/
import { ElasticsearchClient, type Logger } from '@kbn/core/server';
import { IndexStatus } from '../../common/types';
import { IndexStatus } from '../../common';
export const checkIndexStatus = async (
esClient: ElasticsearchClient,

View file

@ -24,7 +24,8 @@ import {
CLOUD_DEFEND_FLEET_PACKAGE_KUERY,
INTEGRATION_PACKAGE_NAME,
} from '../../common/constants';
import { POLICIES_PACKAGE_POLICY_PREFIX, PoliciesQueryParams } from '../../common/schemas/policy';
import { POLICIES_PACKAGE_POLICY_PREFIX } from '../../common/constants';
import type { PoliciesQueryParams } from '../../common';
export const PACKAGE_POLICY_SAVED_OBJECT_TYPE = 'ingest-package-policies';

View file

@ -4,11 +4,10 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { httpServerMock, httpServiceMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
import {
policiesQueryParamsSchema,
DEFAULT_POLICIES_PER_PAGE,
} from '../../../common/schemas/policy';
import { httpServerMock, savedObjectsClientMock } from '@kbn/core/server/mocks';
import { mockRouter } from '@kbn/core-http-router-server-mocks';
import { policiesQueryParamsSchema } from '../../../common';
import { DEFAULT_POLICIES_PER_PAGE } from '../../../common/constants';
import {
PACKAGE_POLICY_SAVED_OBJECT_TYPE,
getCloudDefendPackagePolicies,
@ -30,21 +29,21 @@ describe('policies API', () => {
});
it('validate the API route path', async () => {
const router = httpServiceMock.createRouter();
const router = mockRouter.create();
defineGetPoliciesRoute(router);
const [config] = router.get.mock.calls[0];
const [config] = (router.versioned.get as jest.Mock).mock.calls[0];
expect(config.path).toEqual('/internal/cloud_defend/policies');
});
it('should accept to a user with fleet.all privilege', async () => {
const router = httpServiceMock.createRouter();
const router = mockRouter.create();
defineGetPoliciesRoute(router);
const route = defineGetPoliciesRoute(router);
const [_, handler] = router.get.mock.calls[0];
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockContext = createCloudDefendRequestHandlerContextMock();
const mockResponse = httpServerMock.createResponseFactory();
@ -57,11 +56,11 @@ describe('policies API', () => {
});
it('should reject to a user without fleet.all privilege', async () => {
const router = httpServiceMock.createRouter();
const router = mockRouter.create();
defineGetPoliciesRoute(router);
const route = defineGetPoliciesRoute(router);
const [_, handler] = router.get.mock.calls[0];
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockContext = createCloudDefendRequestHandlerContextMock();
mockContext.fleet.authz.fleet.all = false;

View file

@ -7,8 +7,8 @@
import { transformError } from '@kbn/securitysolution-es-utils';
import type { AgentPolicy, PackagePolicy } from '@kbn/fleet-plugin/common';
import { POLICIES_ROUTE_PATH, INTEGRATION_PACKAGE_NAME } from '../../../common/constants';
import { policiesQueryParamsSchema } from '../../../common/schemas/policy';
import type { CloudDefendPolicy } from '../../../common/types';
import { policiesQueryParamsSchema } from '../../../common';
import type { CloudDefendPolicy } from '../../../common';
import { isNonNullable } from '../../../common/utils/helpers';
import { CloudDefendRouter } from '../../types';
import {
@ -55,61 +55,66 @@ const createPolicies = (
);
};
export const defineGetPoliciesRoute = (router: CloudDefendRouter): void =>
router.get(
{
export const defineGetPoliciesRoute = (router: CloudDefendRouter) =>
router.versioned
.get({
access: 'internal',
path: POLICIES_ROUTE_PATH,
validate: { query: policiesQueryParamsSchema },
options: {
tags: ['access:cloud-defend-read'],
},
},
async (context, request, response) => {
if (!(await context.fleet).authz.fleet.all) {
return response.forbidden();
})
.addVersion(
{
version: '1',
validate: { request: { query: policiesQueryParamsSchema } },
},
async (context, request, response) => {
if (!(await context.fleet).authz.fleet.all) {
return response.forbidden();
}
const cloudDefendContext = await context.cloudDefend;
try {
const cloudDefendPackagePolicies = await getCloudDefendPackagePolicies(
cloudDefendContext.soClient,
cloudDefendContext.packagePolicyService,
INTEGRATION_PACKAGE_NAME,
request.query
);
const agentPolicies = await getCloudDefendAgentPolicies(
cloudDefendContext.soClient,
cloudDefendPackagePolicies.items,
cloudDefendContext.agentPolicyService
);
const agentStatusesByAgentPolicyId = await getAgentStatusesByAgentPolicies(
cloudDefendContext.agentService,
agentPolicies,
cloudDefendContext.logger
);
const policies = await createPolicies(
agentPolicies,
agentStatusesByAgentPolicyId,
cloudDefendPackagePolicies.items
);
return response.ok({
body: {
...cloudDefendPackagePolicies,
items: policies,
},
});
} catch (err) {
const error = transformError(err);
cloudDefendContext.logger.error(`Failed to fetch policies ${err}`);
return response.customError({
body: { message: error.message },
statusCode: error.statusCode,
});
}
}
const cloudDefendContext = await context.cloudDefend;
try {
const cloudDefendPackagePolicies = await getCloudDefendPackagePolicies(
cloudDefendContext.soClient,
cloudDefendContext.packagePolicyService,
INTEGRATION_PACKAGE_NAME,
request.query
);
const agentPolicies = await getCloudDefendAgentPolicies(
cloudDefendContext.soClient,
cloudDefendPackagePolicies.items,
cloudDefendContext.agentPolicyService
);
const agentStatusesByAgentPolicyId = await getAgentStatusesByAgentPolicies(
cloudDefendContext.agentService,
agentPolicies,
cloudDefendContext.logger
);
const policies = await createPolicies(
agentPolicies,
agentStatusesByAgentPolicyId,
cloudDefendPackagePolicies.items
);
return response.ok({
body: {
...cloudDefendPackagePolicies,
items: policies,
},
});
} catch (err) {
const error = transformError(err);
cloudDefendContext.logger.error(`Failed to fetch policies ${err}`);
return response.customError({
body: { message: error.message },
statusCode: error.statusCode,
});
}
}
);
);

View file

@ -6,7 +6,8 @@
*/
import { defineGetCloudDefendStatusRoute, INDEX_TIMEOUT_IN_MINUTES } from './status';
import { httpServerMock, httpServiceMock } from '@kbn/core/server/mocks';
import { httpServerMock } from '@kbn/core/server/mocks';
import { mockRouter } from '@kbn/core-http-router-server-mocks';
import type { ESSearchResponse } from '@kbn/es-types';
import {
AgentClient,
@ -57,7 +58,8 @@ const mockLatestCloudDefendPackageInfo: RegistryPackage = {
};
describe('CloudDefendSetupStatus route', () => {
const router = httpServiceMock.createRouter();
const router = mockRouter.create();
let mockContext: ReturnType<typeof createCloudDefendRequestHandlerContextMock>;
let mockPackagePolicyService: jest.Mocked<PackagePolicyClient>;
let mockAgentPolicyService: jest.Mocked<AgentPolicyServiceInterface>;
@ -81,7 +83,8 @@ describe('CloudDefendSetupStatus route', () => {
it('validate the API route path', async () => {
defineGetCloudDefendStatusRoute(router);
const [config, _] = router.get.mock.calls[0];
const [config, _] = (router.versioned.get as jest.Mock).mock.calls[0];
expect(config.path).toEqual('/internal/cloud_defend/status');
});
@ -132,8 +135,8 @@ describe('CloudDefendSetupStatus route', () => {
});
// Act
defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -169,8 +172,8 @@ describe('CloudDefendSetupStatus route', () => {
});
// Act
defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -181,7 +184,7 @@ describe('CloudDefendSetupStatus route', () => {
const body = call[0]?.body;
expect(mockResponse.ok).toHaveBeenCalledTimes(1);
await expect(body).toMatchObject({
expect(body).toMatchObject({
status: 'indexed',
latestPackageVersion: '1.0.0',
installedPackagePolicies: 0,
@ -210,8 +213,8 @@ describe('CloudDefendSetupStatus route', () => {
});
// Act
defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -223,7 +226,7 @@ describe('CloudDefendSetupStatus route', () => {
expect(mockResponse.ok).toHaveBeenCalledTimes(1);
await expect(body).toMatchObject({
expect(body).toMatchObject({
status: 'indexed',
latestPackageVersion: '1.0.0',
installedPackagePolicies: 3,
@ -261,8 +264,8 @@ describe('CloudDefendSetupStatus route', () => {
} as unknown as GetAgentStatusResponse['results']);
// Act
defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -299,8 +302,8 @@ describe('CloudDefendSetupStatus route', () => {
page: 1,
perPage: 100,
});
defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -351,9 +354,9 @@ describe('CloudDefendSetupStatus route', () => {
} as unknown as GetAgentStatusResponse['results']);
// Act
defineGetCloudDefendStatusRoute(router);
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -408,9 +411,9 @@ describe('CloudDefendSetupStatus route', () => {
} as unknown as GetAgentStatusResponse['results']);
// Act
defineGetCloudDefendStatusRoute(router);
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();
@ -467,9 +470,9 @@ describe('CloudDefendSetupStatus route', () => {
} as unknown as GetAgentStatusResponse['results']);
// Act
defineGetCloudDefendStatusRoute(router);
const route = defineGetCloudDefendStatusRoute(router);
const [_, handler] = router.get.mock.calls[0];
const [_, handler] = (route.addVersion as jest.Mock).mock.calls[0];
const mockResponse = httpServerMock.createResponseFactory();
const mockRequest = httpServerMock.createKibanaRequest();

View file

@ -5,6 +5,13 @@
* 2.0.
*/
/* te
* 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 { transformError } from '@kbn/securitysolution-es-utils';
import type { SavedObjectsClientContract, Logger } from '@kbn/core/server';
import type { AgentPolicyServiceInterface, AgentService } from '@kbn/fleet-plugin/server';
@ -16,11 +23,7 @@ import {
STATUS_ROUTE_PATH,
} from '../../../common/constants';
import type { CloudDefendApiRequestHandlerContext, CloudDefendRouter } from '../../types';
import type {
CloudDefendSetupStatus,
CloudDefendStatusCode,
IndexStatus,
} from '../../../common/types';
import type { CloudDefendSetupStatus, CloudDefendStatusCode, IndexStatus } from '../../../common';
import {
getAgentStatusesByAgentPolicies,
getCloudDefendAgentPolicies,
@ -168,16 +171,16 @@ const getCloudDefendStatus = async ({
return response;
};
export const defineGetCloudDefendStatusRoute = (router: CloudDefendRouter): void =>
router.get(
{
export const defineGetCloudDefendStatusRoute = (router: CloudDefendRouter) =>
router.versioned
.get({
access: 'internal',
path: STATUS_ROUTE_PATH,
validate: {},
options: {
tags: ['access:cloud-defend-read'],
},
},
async (context, request, response) => {
})
.addVersion({ version: '1', validate: {} }, async (context, request, response) => {
const cloudDefendContext = await context.cloudDefend;
try {
const status = await getCloudDefendStatus(cloudDefendContext);
@ -194,5 +197,4 @@ export const defineGetCloudDefendStatusRoute = (router: CloudDefendRouter): void
statusCode: error.statusCode,
});
}
}
);
});

View file

@ -33,7 +33,8 @@
"@kbn/data-views-plugin",
"@kbn/utility-types",
"@kbn/utility-types-jest",
"@kbn/kubernetes-security-plugin"
"@kbn/kubernetes-security-plugin",
"@kbn/core-http-router-server-mocks"
],
"exclude": ["target/**/*"]
}