Skip to content

Commit

Permalink
[Fleet] using @kbn/config-schema part 2 (outputs and other apis) (#19…
Browse files Browse the repository at this point in the history
…3326)

## Summary

Relates #184685
Added tests to verify response schemas.

To verify, go to Fleet settings and try create/update different output
types, fleet server hosts, outputs.

```
GET kbn:/api/oas?pathStartsWith=/api/fleet/outputs
GET kbn:/api/oas?pathStartsWith=/api/fleet/health_check
GET kbn:/api/oas?pathStartsWith=/api/fleet/message_signing_service/rotate_key_pair
GET kbn:/api/oas?pathStartsWith=/api/fleet/fleet_server_hosts
GET kbn:/api/oas?pathStartsWith=/api/fleet/data_streams

# test APIs
POST kbn:/api/fleet/health_check
{
  "id": "default-fleet-server"
}
POST kbn:/api/fleet/message_signing_service/rotate_key_pair
GET kbn:/api/fleet/data_streams
GET kbn:/api/fleet/check-permissions
POST kbn:/api/fleet/service_tokens
```

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
  • Loading branch information
juliaElastic and kibanamachine authored Sep 23, 2024
1 parent 773e747 commit 46dbf51
Show file tree
Hide file tree
Showing 27 changed files with 1,508 additions and 216 deletions.
8 changes: 4 additions & 4 deletions x-pack/plugins/fleet/common/types/models/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ export interface NewElasticsearchOutput extends NewBaseOutput {

export interface NewRemoteElasticsearchOutput extends NewBaseOutput {
type: OutputType['RemoteElasticsearch'];
service_token?: string;
service_token?: string | null;
secrets?: {
service_token?: OutputSecret;
};
Expand Down Expand Up @@ -110,11 +110,11 @@ export interface KafkaOutput extends NewBaseOutput {
compression_level?: number;
auth_type?: ValueOf<KafkaAuthType>;
connection_type?: ValueOf<KafkaConnectionTypeType>;
username?: string;
password?: string;
username?: string | null;
password?: string | null;
sasl?: {
mechanism?: ValueOf<KafkaSaslMechanism>;
};
} | null;
partition?: ValueOf<KafkaPartitionType>;
random?: {
group_events?: number;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -394,12 +394,12 @@ export function useOutputForm(onSucess: () => void, output?: Output, defaultOupu
);

const kafkaAuthUsernameInput = useInput(
kafkaOutput?.username,
kafkaOutput?.username ?? undefined,
kafkaAuthMethodInput.value === kafkaAuthType.Userpass ? validateKafkaUsername : undefined,
isDisabled('username')
);
const kafkaAuthPasswordInput = useInput(
kafkaOutput?.password,
kafkaOutput?.password ?? undefined,
kafkaAuthMethodInput.value === kafkaAuthType.Userpass ? validateKafkaPassword : undefined,
isDisabled('password')
);
Expand Down
81 changes: 81 additions & 0 deletions x-pack/plugins/fleet/server/routes/app/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* 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 { httpServerMock } from '@kbn/core-http-server-mocks';

import type { FleetRequestHandlerContext } from '../../types';
import { CheckPermissionsResponseSchema } from '../../types';

import { xpackMocks } from '../../mocks';

import {
GenerateServiceTokenResponseSchema,
generateServiceTokenHandler,
getCheckPermissionsHandler,
} from '.';

jest.mock('../../services', () => ({
appContextService: {
getSecurityLicense: jest.fn().mockReturnValue({ isEnabled: jest.fn().mockReturnValue(false) }),
getCloud: jest.fn().mockReturnValue({ isServerlessEnabled: false } as any),
getExperimentalFeatures: jest.fn().mockReturnValue({ subfeaturePrivileges: false }),
getLogger: jest.fn().mockReturnValue({ debug: jest.fn(), error: jest.fn() } as any),
},
}));

describe('schema validation', () => {
let context: FleetRequestHandlerContext;
let response: ReturnType<typeof httpServerMock.createResponseFactory>;

beforeEach(() => {
context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext;
response = httpServerMock.createResponseFactory();
});

it('check permissions should return valid response', async () => {
const expectedResponse = {
success: false,
error: 'MISSING_SECURITY',
};
await getCheckPermissionsHandler(context, {} as any, response);

expect(response.ok).toHaveBeenCalledWith({
body: expectedResponse,
});
const validationResp = CheckPermissionsResponseSchema.validate(expectedResponse);
expect(validationResp).toEqual(expectedResponse);
});

it('generate service token should return valid response', async () => {
(
(await context.core).elasticsearch.client.asCurrentUser.transport.request as jest.Mock
).mockResolvedValue({
created: true,
token: {
name: 'token',
value: 'value',
},
});
const expectedResponse = {
name: 'token',
value: 'value',
};
await generateServiceTokenHandler(
context,
{
body: {},
} as any,
response
);

expect(response.ok).toHaveBeenCalledWith({
body: expectedResponse,
});
const validationResp = GenerateServiceTokenResponseSchema.validate(expectedResponse);
expect(validationResp).toEqual(expectedResponse);
});
});
36 changes: 34 additions & 2 deletions x-pack/plugins/fleet/server/routes/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import type { RequestHandler, RouteValidationResultFactory } from '@kbn/core/server';
import type { TypeOf } from '@kbn/config-schema';
import { schema } from '@kbn/config-schema';

import { parseExperimentalConfigValue } from '../../../common/experimental_features';
import type { FleetAuthzRouter } from '../../services/security';
Expand All @@ -16,9 +17,10 @@ import { appContextService } from '../../services';
import type { CheckPermissionsResponse, GenerateServiceTokenResponse } from '../../../common/types';
import { defaultFleetErrorHandler, GenerateServiceTokenError } from '../../errors';
import type { FleetRequestHandler, GenerateServiceTokenRequestSchema } from '../../types';
import { CheckPermissionsRequestSchema } from '../../types';
import { CheckPermissionsRequestSchema, CheckPermissionsResponseSchema } from '../../types';
import { enableSpaceAwarenessMigration } from '../../services/spaces/enable_space_awareness';
import { type FleetConfigType } from '../../config';
import { genericErrorResponse } from '../schema/errors';

export const getCheckPermissionsHandler: FleetRequestHandler<
unknown,
Expand Down Expand Up @@ -189,6 +191,11 @@ const serviceTokenBodyValidation = (data: any, validationResult: RouteValidation
return ok({ remote });
};

export const GenerateServiceTokenResponseSchema = schema.object({
name: schema.string(),
value: schema.string(),
});

export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType) => {
const experimentalFeatures = parseExperimentalConfigValue(config.enableExperimental);

Expand All @@ -212,11 +219,25 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
router.versioned
.get({
path: APP_API_ROUTES.CHECK_PERMISSIONS_PATTERN,
description: `Check permissions`,
options: {
tags: ['oas_tag:Fleet internals'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: { request: CheckPermissionsRequestSchema },
validate: {
request: CheckPermissionsRequestSchema,
response: {
200: {
body: () => CheckPermissionsResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
},
},
getCheckPermissionsHandler
);
Expand Down Expand Up @@ -244,12 +265,23 @@ export const registerRoutes = (router: FleetAuthzRouter, config: FleetConfigType
fleet: { allAgents: true },
},
description: `Create a service token`,
options: {
tags: ['oas_tag:Fleet service tokens'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: {
request: { body: serviceTokenBodyValidation },
response: {
200: {
body: () => GenerateServiceTokenResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
},
},
generateServiceTokenHandler
Expand Down
69 changes: 69 additions & 0 deletions x-pack/plugins/fleet/server/routes/data_streams/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 { httpServerMock } from '@kbn/core-http-server-mocks';

import type { FleetRequestHandlerContext } from '../..';

import { xpackMocks } from '../../mocks';

import { ListDataStreamsResponseSchema } from '.';
import { getListHandler } from './handlers';

jest.mock('./handlers', () => ({
getListHandler: jest.fn(),
}));

const getListHandlerMock = getListHandler as jest.Mock;

describe('schema validation', () => {
let context: FleetRequestHandlerContext;
let response: ReturnType<typeof httpServerMock.createResponseFactory>;

beforeEach(() => {
context = xpackMocks.createRequestHandlerContext() as unknown as FleetRequestHandlerContext;
response = httpServerMock.createResponseFactory();
});

it('list data streams should return valid response', async () => {
const expectedResponse = {
data_streams: [
{
index: 'index',
dataset: 'dataset',
namespace: 'namespace',
type: 'type',
package: 'package',
package_version: '1.0.0',
last_activity_ms: 123,
size_in_bytes: 123,
size_in_bytes_formatted: 123,
dashboards: [
{
id: 'id',
title: 'title',
},
],
serviceDetails: {
environment: 'environment',
serviceName: 'serviceName',
},
},
],
};
getListHandlerMock.mockImplementation((ctx, request, res) => {
return res.ok({ body: expectedResponse });
});
await getListHandler(context, {} as any, response);
expect(response.ok).toHaveBeenCalledWith({
body: expectedResponse,
});

const validateResp = ListDataStreamsResponseSchema.validate(expectedResponse);
expect(validateResp).toEqual(expectedResponse);
});
});
47 changes: 46 additions & 1 deletion x-pack/plugins/fleet/server/routes/data_streams/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,46 @@
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/
import { schema } from '@kbn/config-schema';

import type { FleetAuthzRouter } from '../../services/security';

import { API_VERSIONS } from '../../../common/constants';

import { DATA_STREAM_API_ROUTES } from '../../constants';

import { genericErrorResponse } from '../schema/errors';

import { getListHandler } from './handlers';

export const ListDataStreamsResponseSchema = schema.object({
data_streams: schema.arrayOf(
schema.object({
index: schema.string(),
dataset: schema.string(),
namespace: schema.string(),
type: schema.string(),
package: schema.string(),
package_version: schema.string(),
last_activity_ms: schema.number(),
size_in_bytes: schema.number(),
size_in_bytes_formatted: schema.oneOf([schema.number(), schema.string()]),
dashboards: schema.arrayOf(
schema.object({
id: schema.string(),
title: schema.string(),
})
),
serviceDetails: schema.nullable(
schema.object({
environment: schema.string(),
serviceName: schema.string(),
})
),
})
),
});

export const registerRoutes = (router: FleetAuthzRouter) => {
// List of data streams
router.versioned
Expand All @@ -21,11 +52,25 @@ export const registerRoutes = (router: FleetAuthzRouter) => {
fleetAuthz: {
fleet: { all: true },
},
description: `List data streams`,
options: {
tags: ['oas_tag:Data streams'],
},
})
.addVersion(
{
version: API_VERSIONS.public.v1,
validate: false,
validate: {
request: {},
response: {
200: {
body: () => ListDataStreamsResponseSchema,
},
400: {
body: genericErrorResponse,
},
},
},
},
getListHandler
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const putFleetProxyHandler: RequestHandler<
> = async (context, request, response) => {
try {
const proxyId = request.params.itemId;
const coreContext = await await context.core;
const coreContext = await context.core;
const soClient = coreContext.savedObjects.client;
const esClient = coreContext.elasticsearch.client.asInternalUser;

Expand Down
Loading

0 comments on commit 46dbf51

Please sign in to comment.