[Fleet] Add new APIs and preconfiguration for Fleet server hosts per policy (#142746)

This commit is contained in:
Nicolas Chaulet 2022-10-06 17:40:28 -04:00 committed by GitHub
parent d206d8382a
commit ad95c571b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
35 changed files with 1681 additions and 8 deletions

View file

@ -254,6 +254,7 @@ export class KbnClientSavedObjects {
'epm-packages',
'epm-packages-assets',
'fleet-preconfiguration-deletion-record',
'fleet-fleet-server-host',
];
const newOptions = { types, space: options?.space };

View file

@ -88,6 +88,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"file": "280f28bd48b3ad1f1a9f84c6c0ae6dd5ed1179da",
"file-upload-usage-collection-telemetry": "8478924cf0057bd90df737155b364f98d05420a5",
"fileShare": "3f88784b041bb8728a7f40763a08981828799a75",
"fleet-fleet-server-host": "f00ca963f1bee868806319789cdc33f1f53a97e2",
"fleet-preconfiguration-deletion-record": "7b28f200513c28ae774f1b7d7d7906954e3c6e16",
"graph-workspace": "3342f2cd561afdde8f42f5fb284bf550dee8ebb5",
"guided-onboarding-guide-state": "561db8d481b131a2bbf46b1e534d6ce960255135",

View file

@ -58,6 +58,7 @@ const previouslyRegisteredTypes = [
'fleet-agents',
'fleet-enrollment-api-keys',
'fleet-preconfiguration-deletion-record',
'fleet-fleet-server-host',
'graph-workspace',
'guided-setup-state',
'guided-onboarding-guide-state',

View file

@ -0,0 +1,10 @@
/*
* 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 const FLEET_SERVER_HOST_SAVED_OBJECT_TYPE = 'fleet-fleet-server-host';
export const DEFAULT_FLEET_SERVER_HOST_ID = 'fleet-default-fleet-server-host';

View file

@ -16,6 +16,7 @@ export * from './enrollment_api_key';
export * from './settings';
export * from './preconfiguration';
export * from './download_source';
export * from './fleet_server_policy_config';
export * from './authz';
// TODO: This is the default `index.max_result_window` ES setting, which dictates

View file

@ -88,6 +88,15 @@ export const OUTPUT_API_ROUTES = {
LOGSTASH_API_KEY_PATTERN: `${API_ROOT}/logstash_api_keys`,
};
// Fleet server API routes
export const FLEET_SERVER_HOST_API_ROUTES = {
LIST_PATTERN: `${API_ROOT}/fleet_server_hosts`,
CREATE_PATTERN: `${API_ROOT}/fleet_server_hosts`,
INFO_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`,
UPDATE_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`,
DELETE_PATTERN: `${API_ROOT}/fleet_server_hosts/{itemId}`,
};
// Settings API routes
export const SETTINGS_API_ROUTES = {
INFO_PATTERN: `${API_ROOT}/settings`,

View file

@ -3706,6 +3706,230 @@
}
]
}
},
"/fleet_server_hosts": {
"get": {
"summary": "Fleet Server Hosts - List",
"description": "Return a list of Fleet server host",
"tags": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"$ref": "#/components/schemas/fleet_server_host"
}
},
"total": {
"type": "integer"
},
"page": {
"type": "integer"
},
"perPage": {
"type": "integer"
}
}
}
}
}
}
},
"operationId": "get-fleet-server-hosts"
},
"post": {
"summary": "Fleet Server Hosts - Create",
"description": "Create a new Fleet Server Host",
"tags": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"item": {
"$ref": "#/components/schemas/fleet_server_host"
}
}
}
}
}
}
},
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"is_default": {
"type": "boolean"
},
"host_urls": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"name",
"host_urls"
]
}
}
}
},
"operationId": "post-fleet-server-hosts"
}
},
"/fleet_server_hosts/{itemId}": {
"get": {
"summary": "Fleet Server Hosts - Info",
"tags": [],
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"item": {
"$ref": "#/components/schemas/fleet_server_host"
}
},
"required": [
"item"
]
}
}
}
}
},
"operationId": "get-one-fleet-server-hosts"
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "itemId",
"in": "path",
"required": true
}
],
"delete": {
"summary": "Fleet Server Hosts - Delete",
"operationId": "delete-fleet-server-hosts",
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"id": {
"type": "string"
}
},
"required": [
"id"
]
}
}
}
}
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "itemId",
"in": "path",
"required": true
},
{
"$ref": "#/components/parameters/kbn_xsrf"
}
]
},
"put": {
"summary": "Fleet Server Hosts - Update",
"operationId": "update-fleet-server-hosts",
"requestBody": {
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"name": {
"type": "string"
},
"is_default": {
"type": "boolean"
},
"host_urls": {
"type": "array",
"items": {
"type": "string"
}
}
}
}
}
}
},
"responses": {
"200": {
"description": "OK",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"item": {
"$ref": "#/components/schemas/fleet_server_host"
}
},
"required": [
"item"
]
}
}
}
}
},
"parameters": [
{
"schema": {
"type": "string"
},
"name": "itemId",
"in": "path",
"required": true
},
{
"$ref": "#/components/parameters/kbn_xsrf"
}
]
}
}
},
"components": {
@ -3977,9 +4201,6 @@
"has_seen_add_data_notice": {
"type": "boolean"
},
"has_seen_fleet_migration_notice": {
"type": "boolean"
},
"fleet_server_hosts": {
"type": "array",
"items": {
@ -5470,6 +5691,37 @@
"name",
"host"
]
},
"fleet_server_host": {
"title": "Fleet Server Host",
"type": "object",
"properties": {
"id": {
"type": "string"
},
"name": {
"type": "string"
},
"is_default": {
"type": "boolean"
},
"is_preconfigured": {
"type": "boolean"
},
"host_urls": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"fleet_server_hosts",
"id",
"is_default",
"is_preconfigured",
"host_urls"
]
}
}
},

View file

@ -2289,6 +2289,145 @@ paths:
operationId: generate-logstash-api-key
parameters:
- $ref: '#/components/parameters/kbn_xsrf'
/fleet_server_hosts:
get:
summary: Fleet Server Hosts - List
description: Return a list of Fleet server host
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: '#/components/schemas/fleet_server_host'
total:
type: integer
page:
type: integer
perPage:
type: integer
operationId: get-fleet-server-hosts
post:
summary: Fleet Server Hosts - Create
description: Create a new Fleet Server Host
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item:
$ref: '#/components/schemas/fleet_server_host'
requestBody:
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: string
is_default:
type: boolean
host_urls:
type: array
items:
type: string
required:
- name
- host_urls
operationId: post-fleet-server-hosts
/fleet_server_hosts/{itemId}:
get:
summary: Fleet Server Hosts - Info
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item:
$ref: '#/components/schemas/fleet_server_host'
required:
- item
operationId: get-one-fleet-server-hosts
parameters:
- schema:
type: string
name: itemId
in: path
required: true
delete:
summary: Fleet Server Hosts - Delete
operationId: delete-fleet-server-hosts
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
id:
type: string
required:
- id
parameters:
- schema:
type: string
name: itemId
in: path
required: true
- $ref: '#/components/parameters/kbn_xsrf'
put:
summary: Fleet Server Hosts - Update
operationId: update-fleet-server-hosts
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
is_default:
type: boolean
host_urls:
type: array
items:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item:
$ref: '#/components/schemas/fleet_server_host'
required:
- item
parameters:
- schema:
type: string
name: itemId
in: path
required: true
- $ref: '#/components/parameters/kbn_xsrf'
components:
securitySchemes:
basicAuth:
@ -2474,8 +2613,6 @@ components:
type: string
has_seen_add_data_notice:
type: boolean
has_seen_fleet_migration_notice:
type: boolean
fleet_server_hosts:
type: array
items:
@ -3509,5 +3646,27 @@ components:
- is_default
- name
- host
fleet_server_host:
title: Fleet Server Host
type: object
properties:
id:
type: string
name:
type: string
is_default:
type: boolean
is_preconfigured:
type: boolean
host_urls:
type: array
items:
type: string
required:
- fleet_server_hosts
- id
- is_default
- is_preconfigured
- host_urls
security:
- basicAuth: []

View file

@ -0,0 +1,21 @@
title: Fleet Server Host
type: object
properties:
id:
type: string
name:
type: string
is_default:
type: boolean
is_preconfigured:
type: boolean
host_urls:
type: array
items:
type: string
required:
- fleet_server_hosts
- id
- is_default
- is_preconfigured
- host_urls

View file

@ -124,6 +124,11 @@ paths:
$ref: paths/agent_download_sources@{source_id}.yaml
/logstash_api_keys:
$ref: paths/logstash_api_keys.yaml
# Fleet server hosts
/fleet_server_hosts:
$ref: paths/fleet_server_hosts.yaml
/fleet_server_hosts/{itemId}:
$ref: paths/fleet_server_hosts@{item_id}.yaml
components:
securitySchemes:
basicAuth:

View file

@ -0,0 +1,57 @@
get:
summary: Fleet Server Hosts - List
description: Return a list of Fleet server hosts
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
$ref: ../components/schemas/fleet_server_host.yaml
total:
type: integer
page:
type: integer
perPage:
type: integer
operationId: get-fleet-server-hosts
post:
summary: Fleet Server Hosts - Create
description: 'Create a new Fleet Server Host'
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item:
$ref: ../components/schemas/fleet_server_host.yaml
requestBody:
content:
application/json:
schema:
type: object
properties:
id:
type: string
name:
type: string
is_default:
type: boolean
host_urls:
type: array
items:
type: string
required:
- name
- host_urls
operationId: post-fleet-server-hosts

View file

@ -0,0 +1,80 @@
get:
summary: Fleet Server Hosts - Info
tags: []
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item:
$ref: ../components/schemas/fleet_server_host.yaml
required:
- item
operationId: get-one-fleet-server-hosts
parameters:
- schema:
type: string
name: itemId
in: path
required: true
delete:
summary: Fleet Server Hosts - Delete
operationId: delete-fleet-server-hosts
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
id:
type: string
required:
- id
parameters:
- schema:
type: string
name: itemId
in: path
required: true
- $ref: ../components/headers/kbn_xsrf.yaml
put:
summary: Fleet Server Hosts - Update
operationId: update-fleet-server-hosts
requestBody:
content:
application/json:
schema:
type: object
properties:
name:
type: string
is_default:
type: boolean
host_urls:
type: array
items:
type: string
responses:
'200':
description: OK
content:
application/json:
schema:
type: object
properties:
item:
$ref: ../components/schemas/fleet_server_host.yaml
required:
- item
parameters:
- schema:
type: string
name: itemId
in: path
required: true
- $ref: ../components/headers/kbn_xsrf.yaml

View file

@ -0,0 +1,19 @@
/*
* 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 interface NewFleetServerHost {
name: string;
host_urls: string[];
is_default: boolean;
is_preconfigured: boolean;
}
export interface FleetServerHost extends NewFleetServerHost {
id: string;
}
export type FleetServerHostSOAttributes = NewFleetServerHost;

View file

@ -16,3 +16,4 @@ export * from './enrollment_api_key';
export * from './settings';
export * from './preconfiguration';
export * from './download_sources';
export * from './fleet_server_policy_config';

View file

@ -0,0 +1,12 @@
/*
* 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 type { FleetServerHost } from '../models';
import type { ListResult } from './common';
export type GetFleetServerHostsResponse = ListResult<FleetServerHost>;

View file

@ -21,6 +21,7 @@ import {
PreconfiguredPackagesSchema,
PreconfiguredAgentPoliciesSchema,
PreconfiguredOutputsSchema,
PreconfiguredFleetServerHostsSchema,
} from './types';
const DEFAULT_BUNDLED_PACKAGE_LOCATION = path.join(__dirname, '../target/bundled_packages');
@ -115,6 +116,7 @@ export const config: PluginConfigDescriptor = {
packages: PreconfiguredPackagesSchema,
agentPolicies: PreconfiguredAgentPoliciesSchema,
outputs: PreconfiguredOutputsSchema,
fleetServerHosts: PreconfiguredFleetServerHostsSchema,
agentIdVerificationEnabled: schema.boolean({ defaultValue: true }),
developer: schema.object({
disableRegistryVersionCheck: schema.boolean({ defaultValue: false }),

View file

@ -64,6 +64,9 @@ export {
DEFAULT_DOWNLOAD_SOURCE_URI,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
DEFAULT_DOWNLOAD_SOURCE_ID,
// Fleet server host
DEFAULT_FLEET_SERVER_HOST_ID,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
// Authz
ENDPOINT_PRIVILEGES,
} from '../../common/constants';

View file

@ -76,6 +76,8 @@ export class OutputInvalidError extends FleetError {}
export class OutputLicenceError extends FleetError {}
export class DownloadSourceError extends FleetError {}
export class FleetServerHostUnauthorizedError extends FleetError {}
export class ArtifactsClientError extends FleetError {}
export class ArtifactsClientAccessDeniedError extends FleetError {
constructor(deniedPackageName: string, allowedPackageName: string) {

View file

@ -64,6 +64,7 @@ import {
ASSETS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
} from './constants';
import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects';
import {
@ -80,6 +81,7 @@ import {
registerPreconfigurationRoutes,
registerDownloadSourcesRoutes,
registerHealthCheckRoutes,
registerFleetServerHostRoutes,
} from './routes';
import type { ExternalCallback, FleetRequestHandlerContext } from './types';
@ -161,6 +163,7 @@ const allSavedObjectTypes = [
ASSETS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
];
/**
@ -399,6 +402,7 @@ export class FleetPlugin
registerSettingsRoutes(fleetAuthzRouter);
registerDataStreamRoutes(fleetAuthzRouter);
registerPreconfigurationRoutes(fleetAuthzRouter);
registerFleetServerHostRoutes(fleetAuthzRouter);
registerDownloadSourcesRoutes(fleetAuthzRouter);
registerHealthCheckRoutes(fleetAuthzRouter);

View file

@ -0,0 +1,143 @@
/*
* 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 type { TypeOf } from '@kbn/config-schema';
import type { RequestHandler } from '@kbn/core/server';
import { SavedObjectsErrorHelpers } from '@kbn/core/server';
import { defaultFleetErrorHandler } from '../../errors';
import { agentPolicyService } from '../../services';
import {
createFleetServerHost,
deleteFleetServerHost,
getFleetServerHost,
listFleetServerHosts,
updateFleetServerHost,
} from '../../services/fleet_server_host';
import type {
GetOneFleetServerHostRequestSchema,
PostFleetServerHostRequestSchema,
PutFleetServerHostRequestSchema,
} from '../../types';
export const postFleetServerHost: RequestHandler<
undefined,
undefined,
TypeOf<typeof PostFleetServerHostRequestSchema.body>
> = async (context, request, response) => {
const coreContext = await context.core;
const soClient = coreContext.savedObjects.client;
const esClient = coreContext.elasticsearch.client.asInternalUser;
try {
const { id, ...data } = request.body;
const FleetServerHost = await createFleetServerHost(
soClient,
{ ...data, is_preconfigured: false },
{ id }
);
if (FleetServerHost.is_default) {
await agentPolicyService.bumpAllAgentPolicies(soClient, esClient);
}
const body = {
item: FleetServerHost,
};
return response.ok({ body });
} catch (error) {
return defaultFleetErrorHandler({ error, response });
}
};
export const getFleetServerPolicyHandler: RequestHandler<
TypeOf<typeof GetOneFleetServerHostRequestSchema.params>
> = async (context, request, response) => {
const soClient = (await context.core).savedObjects.client;
try {
const item = await getFleetServerHost(soClient, request.params.itemId);
const body = {
item,
};
return response.ok({ body });
} catch (error) {
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
return response.notFound({
body: { message: `Fleet server ${request.params.itemId} not found` },
});
}
return defaultFleetErrorHandler({ error, response });
}
};
export const deleteFleetServerPolicyHandler: RequestHandler<
TypeOf<typeof GetOneFleetServerHostRequestSchema.params>
> = async (context, request, response) => {
const soClient = (await context.core).savedObjects.client;
try {
await deleteFleetServerHost(soClient, request.params.itemId);
const body = {
id: request.params.itemId,
};
return response.ok({ body });
} catch (error) {
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
return response.notFound({
body: { message: `Fleet server ${request.params.itemId} not found` },
});
}
return defaultFleetErrorHandler({ error, response });
}
};
export const putFleetServerPolicyHandler: RequestHandler<
TypeOf<typeof PutFleetServerHostRequestSchema.params>,
undefined,
TypeOf<typeof PutFleetServerHostRequestSchema.body>
> = async (context, request, response) => {
const soClient = (await context.core).savedObjects.client;
try {
const item = await updateFleetServerHost(soClient, request.params.itemId, request.body);
const body = {
item,
};
return response.ok({ body });
} catch (error) {
if (SavedObjectsErrorHelpers.isNotFoundError(error)) {
return response.notFound({
body: { message: `Fleet server ${request.params.itemId} not found` },
});
}
return defaultFleetErrorHandler({ error, response });
}
};
export const getAllFleetServerPolicyHandler: RequestHandler<
TypeOf<typeof PutFleetServerHostRequestSchema.params>,
undefined,
TypeOf<typeof PutFleetServerHostRequestSchema.body>
> = async (context, request, response) => {
const soClient = (await context.core).savedObjects.client;
try {
const res = await listFleetServerHosts(soClient);
const body = {
items: res.items,
page: res.page,
perPage: res.perPage,
total: res.total,
};
return response.ok({ body });
} catch (error) {
return defaultFleetErrorHandler({ error, response });
}
};

View file

@ -0,0 +1,77 @@
/*
* 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 { FLEET_SERVER_HOST_API_ROUTES } from '../../../common/constants';
import {
GetAllFleetServerHostRequestSchema,
GetOneFleetServerHostRequestSchema,
PostFleetServerHostRequestSchema,
PutFleetServerHostRequestSchema,
} from '../../types';
import type { FleetAuthzRouter } from '../security';
import {
deleteFleetServerPolicyHandler,
getAllFleetServerPolicyHandler,
getFleetServerPolicyHandler,
postFleetServerHost,
putFleetServerPolicyHandler,
} from './handler';
export const registerRoutes = (router: FleetAuthzRouter) => {
router.get(
{
path: FLEET_SERVER_HOST_API_ROUTES.LIST_PATTERN,
validate: GetAllFleetServerHostRequestSchema,
fleetAuthz: {
fleet: { all: true },
},
},
getAllFleetServerPolicyHandler
);
router.post(
{
path: FLEET_SERVER_HOST_API_ROUTES.CREATE_PATTERN,
validate: PostFleetServerHostRequestSchema,
fleetAuthz: {
fleet: { all: true },
},
},
postFleetServerHost
);
router.get(
{
path: FLEET_SERVER_HOST_API_ROUTES.INFO_PATTERN,
validate: GetOneFleetServerHostRequestSchema,
fleetAuthz: {
fleet: { all: true },
},
},
getFleetServerPolicyHandler
);
router.delete(
{
path: FLEET_SERVER_HOST_API_ROUTES.DELETE_PATTERN,
validate: GetOneFleetServerHostRequestSchema,
fleetAuthz: {
fleet: { all: true },
},
},
deleteFleetServerPolicyHandler
);
router.put(
{
path: FLEET_SERVER_HOST_API_ROUTES.UPDATE_PATTERN,
validate: PutFleetServerHostRequestSchema,
fleetAuthz: {
fleet: { all: true },
},
},
putFleetServerPolicyHandler
);
};

View file

@ -18,3 +18,4 @@ export { registerRoutes as registerAppRoutes } from './app';
export { registerRoutes as registerPreconfigurationRoutes } from './preconfiguration';
export { registerRoutes as registerDownloadSourcesRoutes } from './download_source';
export { registerRoutes as registerHealthCheckRoutes } from './health_check';
export { registerRoutes as registerFleetServerHostRoutes } from './fleet_server_policy_config';

View file

@ -18,6 +18,7 @@ import {
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
DOWNLOAD_SOURCE_SAVED_OBJECT_TYPE,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
} from '../constants';
import {
@ -344,6 +345,22 @@ const getSavedObjectTypes = (
},
},
},
[FLEET_SERVER_HOST_SAVED_OBJECT_TYPE]: {
name: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
hidden: false,
namespaceType: 'agnostic',
management: {
importableAndExportable: false,
},
mappings: {
properties: {
name: { type: 'keyword' },
is_default: { type: 'boolean' },
host_urls: { type: 'keyword', index: false },
is_preconfigured: { type: 'boolean' },
},
},
},
});
export function registerSavedObjects(

View file

@ -0,0 +1,145 @@
/*
* 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 { savedObjectsClientMock } from '@kbn/core/server/mocks';
import {
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
DEFAULT_FLEET_SERVER_HOST_ID,
} from '../constants';
import { appContextService } from './app_context';
import { migrateSettingsToFleetServerHost } from './fleet_server_host';
import { getCloudFleetServersHosts } from './settings';
jest.mock('./app_context');
const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;
describe('getCloudFleetServersHosts', () => {
afterEach(() => {
mockedAppContextService.getCloud.mockReset();
});
it('should return undefined if cloud is not setup', () => {
expect(getCloudFleetServersHosts()).toBeUndefined();
});
it('should return fleet server hosts if cloud is correctly setup with default port == 443', () => {
mockedAppContextService.getCloud.mockReturnValue({
cloudId:
'dXMtZWFzdC0xLmF3cy5mb3VuZC5pbyRjZWM2ZjI2MWE3NGJmMjRjZTMzYmI4ODExYjg0Mjk0ZiRjNmMyY2E2ZDA0MjI0OWFmMGNjN2Q3YTllOTYyNTc0Mw==',
isCloudEnabled: true,
deploymentId: 'deployment-id-1',
apm: {},
});
expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(`
Array [
"https://deployment-id-1.fleet.us-east-1.aws.found.io",
]
`);
});
it('should return fleet server hosts if cloud is correctly setup with a default port', () => {
mockedAppContextService.getCloud.mockReturnValue({
cloudId:
'test:dGVzdC5mcjo5MjQzJGRhM2I2YjNkYWY5ZDRjODE4ZjI4ZmEzNDdjMzgzODViJDgxMmY4NWMxZjNjZTQ2YTliYjgxZjFjMWIxMzRjNmRl',
isCloudEnabled: true,
deploymentId: 'deployment-id-1',
apm: {},
});
expect(getCloudFleetServersHosts()).toMatchInlineSnapshot(`
Array [
"https://deployment-id-1.fleet.test.fr:9243",
]
`);
});
});
describe('migrateSettingsToFleetServerHost', () => {
it('should not migrate settings if a default fleet server policy config exists', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockImplementation(({ type }) => {
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
return { saved_objects: [{ id: 'test123' }] } as any;
}
throw new Error('Not mocked');
});
await migrateSettingsToFleetServerHost(soClient);
expect(soClient.create).not.toBeCalled();
});
it('should not migrate settings if there is not old settings', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockImplementation(({ type }) => {
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
return { saved_objects: [] } as any;
}
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
return {
saved_objects: [],
} as any;
}
throw new Error('Not mocked');
});
soClient.create.mockResolvedValue({
id: DEFAULT_FLEET_SERVER_HOST_ID,
attributes: {},
} as any);
await migrateSettingsToFleetServerHost(soClient);
expect(soClient.create).not.toBeCalled();
});
it('should migrate settings to new saved object', async () => {
const soClient = savedObjectsClientMock.create();
soClient.find.mockImplementation(({ type }) => {
if (type === FLEET_SERVER_HOST_SAVED_OBJECT_TYPE) {
return { saved_objects: [] } as any;
}
if (type === GLOBAL_SETTINGS_SAVED_OBJECT_TYPE) {
return {
saved_objects: [
{
attributes: {
fleet_server_hosts: ['https://fleetserver:8220'],
},
},
],
} as any;
}
throw new Error('Not mocked');
});
soClient.create.mockResolvedValue({
id: DEFAULT_FLEET_SERVER_HOST_ID,
attributes: {},
} as any);
await migrateSettingsToFleetServerHost(soClient);
expect(soClient.create).toBeCalledWith(
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
expect.objectContaining({
is_default: true,
host_urls: ['https://fleetserver:8220'],
}),
expect.objectContaining({
id: DEFAULT_FLEET_SERVER_HOST_ID,
})
);
});
});

View file

@ -0,0 +1,237 @@
/*
* 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 type { SavedObjectsClientContract } from '@kbn/core/server';
import {
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
DEFAULT_FLEET_SERVER_HOST_ID,
SO_SEARCH_LIMIT,
} from '../constants';
import type {
SettingsSOAttributes,
FleetServerHostSOAttributes,
FleetServerHost,
NewFleetServerHost,
} from '../types';
import { FleetServerHostUnauthorizedError } from '../errors';
export async function createFleetServerHost(
soClient: SavedObjectsClientContract,
data: NewFleetServerHost,
options?: { id?: string; overwrite?: boolean; fromPreconfiguration?: boolean }
): Promise<FleetServerHost> {
if (data.is_default) {
const defaultItem = await getDefaultFleetServerHost(soClient);
if (defaultItem) {
await updateFleetServerHost(
soClient,
defaultItem.id,
{ is_default: false },
{ fromPreconfiguration: options?.fromPreconfiguration }
);
}
}
const res = await soClient.create<FleetServerHostSOAttributes>(
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
data,
{ id: options?.id, overwrite: options?.overwrite }
);
return {
id: res.id,
...res.attributes,
};
}
export async function getFleetServerHost(
soClient: SavedObjectsClientContract,
id: string
): Promise<FleetServerHost> {
const res = await soClient.get<FleetServerHostSOAttributes>(
FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
id
);
return {
id: res.id,
...res.attributes,
};
}
export async function listFleetServerHosts(soClient: SavedObjectsClientContract) {
const res = await soClient.find<FleetServerHostSOAttributes>({
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
perPage: SO_SEARCH_LIMIT,
});
return {
items: res.saved_objects.map<FleetServerHost>((so) => ({
id: so.id,
...so.attributes,
})),
total: res.total,
page: res.page,
perPage: res.per_page,
};
}
export async function deleteFleetServerHost(
soClient: SavedObjectsClientContract,
id: string,
options?: { fromPreconfiguration?: boolean }
) {
const fleetServerHost = await getFleetServerHost(soClient, id);
if (fleetServerHost.is_preconfigured && !options?.fromPreconfiguration) {
throw new FleetServerHostUnauthorizedError(
`Cannot delete ${id} preconfigured fleet server host`
);
}
if (fleetServerHost.is_default) {
throw new FleetServerHostUnauthorizedError(
`Default Fleet Server hosts ${id} cannot be deleted.`
);
}
return await soClient.delete(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id);
}
export async function updateFleetServerHost(
soClient: SavedObjectsClientContract,
id: string,
data: Partial<FleetServerHost>,
options?: { fromPreconfiguration?: boolean }
) {
const originalItem = await getFleetServerHost(soClient, id);
if (data.is_preconfigured && !options?.fromPreconfiguration) {
throw new FleetServerHostUnauthorizedError(
`Cannot update ${id} preconfigured fleet server host`
);
}
if (data.is_default) {
const defaultItem = await getDefaultFleetServerHost(soClient);
if (defaultItem && defaultItem.id !== id) {
await updateFleetServerHost(
soClient,
defaultItem.id,
{
is_default: false,
},
{ fromPreconfiguration: options?.fromPreconfiguration }
);
}
}
await soClient.update<FleetServerHostSOAttributes>(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id, data);
return {
...originalItem,
...data,
};
}
export async function bulkGetFleetServerHosts(
soClient: SavedObjectsClientContract,
ids: string[],
{ ignoreNotFound = false } = { ignoreNotFound: true }
) {
if (ids.length === 0) {
return [];
}
const res = await soClient.bulkGet<FleetServerHostSOAttributes>(
ids.map((id) => ({
id,
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
}))
);
return res.saved_objects
.map((so) => {
if (so.error) {
if (!ignoreNotFound || so.error.statusCode !== 404) {
throw so.error;
}
return undefined;
}
return {
id: so.id,
...so.attributes,
};
})
.filter(
(fleetServerHostOrUndefined): fleetServerHostOrUndefined is FleetServerHost =>
typeof fleetServerHostOrUndefined !== 'undefined'
);
}
/**
* Get the default Fleet server policy hosts or throw if it does not exists
*/
export async function getDefaultFleetServerHost(
soClient: SavedObjectsClientContract
): Promise<FleetServerHost | null> {
const res = await soClient.find<FleetServerHostSOAttributes>({
type: FLEET_SERVER_HOST_SAVED_OBJECT_TYPE,
filter: `${FLEET_SERVER_HOST_SAVED_OBJECT_TYPE}.attributes.is_default:true`,
});
if (res.saved_objects.length === 0) {
return null;
}
return {
id: res.saved_objects[0].id,
...res.saved_objects[0].attributes,
};
}
/**
* Migrate Global setting fleet server hosts to their own saved object
*/
export async function migrateSettingsToFleetServerHost(soClient: SavedObjectsClientContract) {
const defaultFleetServerHost = await getDefaultFleetServerHost(soClient);
if (defaultFleetServerHost) {
return;
}
const res = await soClient.find<SettingsSOAttributes>({
type: GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
});
const oldSettings = res.saved_objects[0];
if (
!oldSettings ||
!oldSettings.attributes.fleet_server_hosts ||
oldSettings.attributes.fleet_server_hosts.length === 0
) {
return;
}
// Migrate
await createFleetServerHost(
soClient,
{
name: 'Default',
host_urls: oldSettings.attributes.fleet_server_hosts,
is_default: true,
is_preconfigured: false,
},
{
id: DEFAULT_FLEET_SERVER_HOST_ID,
overwrite: true,
}
);
}

View file

@ -59,3 +59,6 @@ export { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration';
// Package Services
export { PackageServiceImpl } from './epm';
export type { PackageService, PackageClient } from './epm';
// Fleet server policy config
export { migrateSettingsToFleetServerHost } from './fleet_server_host';

View file

@ -0,0 +1,83 @@
/*
* 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 { getPreconfiguredFleetServerHostFromConfig } from './fleet_server_host';
jest.mock('../fleet_server_host');
describe('getPreconfiguredFleetServerHostFromConfig', () => {
it('should work with preconfigured fleetServerHosts', () => {
const config = {
fleetServerHosts: [
{
id: 'fleet-123',
name: 'TEST',
is_default: true,
host_urls: ['http://test.fr'],
},
],
};
const res = getPreconfiguredFleetServerHostFromConfig(config);
expect(res).toEqual(config.fleetServerHosts);
});
it('should work with agents.fleet_server.hosts', () => {
const config = {
agents: { fleet_server: { hosts: ['http://test.fr'] } },
};
const res = getPreconfiguredFleetServerHostFromConfig(config);
expect(res).toEqual([
{
id: 'fleet-default-fleet-server-host',
name: 'Default',
host_urls: ['http://test.fr'],
is_default: true,
},
]);
});
it('should work with agents.fleet_server.hosts and preconfigured outputs', () => {
const config = {
agents: { fleet_server: { hosts: ['http://test.fr'] } },
fleetServerHosts: [
{
id: 'fleet-123',
name: 'TEST',
is_default: false,
host_urls: ['http://test.fr'],
},
],
};
const res = getPreconfiguredFleetServerHostFromConfig(config);
expect(res).toHaveLength(2);
expect(res.map(({ id }) => id)).toEqual(['fleet-123', 'fleet-default-fleet-server-host']);
});
it('should throw if there is multiple default outputs', () => {
const config = {
agents: { fleet_server: { hosts: ['http://test.fr'] } },
fleetServerHosts: [
{
id: 'fleet-123',
name: 'TEST',
is_default: true,
host_urls: ['http://test.fr'],
},
],
};
expect(() => getPreconfiguredFleetServerHostFromConfig(config)).toThrowError(
/Only one default Fleet Server host is allowed/
);
});
});

View file

@ -0,0 +1,145 @@
/*
* 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 type { SavedObjectsClientContract } from '@kbn/core/server';
import { isEqual } from 'lodash';
import type { FleetConfigType } from '../../config';
import { DEFAULT_FLEET_SERVER_HOST_ID } from '../../constants';
import type { FleetServerHost } from '../../types';
import {
bulkGetFleetServerHosts,
createFleetServerHost,
deleteFleetServerHost,
listFleetServerHosts,
updateFleetServerHost,
} from '../fleet_server_host';
export function getPreconfiguredFleetServerHostFromConfig(config?: FleetConfigType) {
const { fleetServerHosts: fleetServerHostsFromConfig } = config;
const legacyFleetServerHostsConfig = getConfigFleetServerHosts(config);
const fleetServerHosts: FleetServerHost[] = (fleetServerHostsFromConfig || []).concat([
...(legacyFleetServerHostsConfig
? [
{
name: 'Default',
is_default: true,
id: DEFAULT_FLEET_SERVER_HOST_ID,
host_urls: legacyFleetServerHostsConfig,
},
]
: []),
]);
if (fleetServerHosts.filter((fleetServerHost) => fleetServerHost.is_default).length > 1) {
throw new Error('Only one default Fleet Server host is allowed');
}
return fleetServerHosts;
}
export async function ensurePreconfiguredFleetServerHosts(
soClient: SavedObjectsClientContract,
preconfiguredFleetServerHosts: FleetServerHost[]
) {
await createOrUpdatePreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts);
await cleanPreconfiguredFleetServerHosts(soClient, preconfiguredFleetServerHosts);
}
export async function createOrUpdatePreconfiguredFleetServerHosts(
soClient: SavedObjectsClientContract,
preconfiguredFleetServerHosts: FleetServerHost[]
) {
const existingFleetServerHosts = await bulkGetFleetServerHosts(
soClient,
preconfiguredFleetServerHosts.map(({ id }) => id),
{ ignoreNotFound: true }
);
await Promise.all(
preconfiguredFleetServerHosts.map(async (preconfiguredFleetServerHost) => {
const existingHost = existingFleetServerHosts.find(
(fleetServerHost) => fleetServerHost.id === preconfiguredFleetServerHost.id
);
const { id, ...data } = preconfiguredFleetServerHost;
const isCreate = !existingHost;
const isUpdateWithNewData =
existingHost &&
(!existingHost.is_preconfigured ||
existingHost.is_default !== preconfiguredFleetServerHost.is_default ||
existingHost.name !== preconfiguredFleetServerHost.name ||
!isEqual(existingHost?.host_urls, preconfiguredFleetServerHost.host_urls));
if (isCreate) {
await createFleetServerHost(
soClient,
{
...data,
is_preconfigured: true,
},
{ id, overwrite: true, fromPreconfiguration: true }
);
} else if (isUpdateWithNewData) {
await updateFleetServerHost(
soClient,
id,
{
...data,
is_preconfigured: true,
},
{ fromPreconfiguration: true }
);
// TODO Bump revision of all policies using that output
}
})
);
}
export async function cleanPreconfiguredFleetServerHosts(
soClient: SavedObjectsClientContract,
preconfiguredFleetServerHosts: FleetServerHost[]
) {
const existingFleetServerHosts = await listFleetServerHosts(soClient);
const existingPreconfiguredHosts = existingFleetServerHosts.items.filter(
(o) => o.is_preconfigured === true
);
for (const existingFleetServerHost of existingPreconfiguredHosts) {
const hasBeenDelete = !preconfiguredFleetServerHosts.find(
({ id }) => existingFleetServerHost.id === id
);
if (!hasBeenDelete) {
continue;
}
if (existingFleetServerHost.is_default) {
await updateFleetServerHost(
soClient,
existingFleetServerHost.id,
{ is_preconfigured: false },
{
fromPreconfiguration: true,
}
);
} else {
await deleteFleetServerHost(soClient, existingFleetServerHost.id, {
fromPreconfiguration: true,
});
}
}
}
function getConfigFleetServerHosts(config?: FleetConfigType) {
return config?.agents?.fleet_server?.hosts && config.agents.fleet_server.hosts.length > 0
? config?.agents?.fleet_server?.hosts
: undefined;
}

View file

@ -44,6 +44,11 @@ import { upgradeManagedPackagePolicies } from './managed_package_policies';
import { getBundledPackages } from './epm/packages';
import { upgradePackageInstallVersion } from './setup/upgrade_package_install_version';
import { upgradeAgentPolicySchemaVersion } from './setup/upgrade_agent_policy_schema_version';
import { migrateSettingsToFleetServerHost } from './fleet_server_host';
import {
ensurePreconfiguredFleetServerHosts,
getPreconfiguredFleetServerHostFromConfig,
} from './preconfiguration/fleet_server_host';
export interface SetupStatus {
isInitialized: boolean;
@ -70,25 +75,32 @@ async function createSetupSideEffects(
const { agentPolicies: policiesOrUndefined, packages: packagesOrUndefined } =
appContextService.getConfig() ?? {};
const policies = policiesOrUndefined ?? [];
let packages = packagesOrUndefined ?? [];
logger.debug('Setting Fleet server config');
await migrateSettingsToFleetServerHost(soClient);
logger.debug('Setting up Fleet download source');
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
logger.debug('Setting up Fleet outputs');
await ensurePreconfiguredFleetServerHosts(
soClient,
getPreconfiguredFleetServerHostFromConfig(appContextService.getConfig())
);
await Promise.all([
ensurePreconfiguredOutputs(
soClient,
esClient,
getPreconfiguredOutputFromConfig(appContextService.getConfig())
),
settingsService.settingsSetup(soClient),
]);
const defaultOutput = await outputService.ensureDefaultOutput(soClient);
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
if (appContextService.getConfig()?.agentIdVerificationEnabled) {
logger.debug('Setting up Fleet Elasticsearch assets');
await ensureFleetGlobalEsAssets(soClient, esClient);

View file

@ -36,6 +36,9 @@ export type {
OutputType,
EnrollmentAPIKey,
EnrollmentAPIKeySOAttributes,
NewFleetServerHost,
FleetServerHost,
FleetServerHostSOAttributes,
Installation,
EpmPackageInstallStatus,
InstallationStatus,

View file

@ -85,6 +85,16 @@ export const PreconfiguredOutputsSchema = schema.arrayOf(
}
);
export const PreconfiguredFleetServerHostsSchema = schema.arrayOf(
schema.object({
id: schema.string(),
name: schema.string(),
is_default: schema.boolean({ defaultValue: false }),
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
}),
{ defaultValue: [] }
);
export const PreconfiguredAgentPoliciesSchema = schema.arrayOf(
schema.object({
...AgentPolicyBaseSchema,

View file

@ -0,0 +1,32 @@
/*
* 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';
export const PostFleetServerHostRequestSchema = {
body: schema.object({
id: schema.maybe(schema.string()),
name: schema.string(),
host_urls: schema.arrayOf(schema.string(), { minSize: 1 }),
is_default: schema.boolean({ defaultValue: false }),
}),
};
export const GetOneFleetServerHostRequestSchema = {
params: schema.object({ itemId: schema.string() }),
};
export const PutFleetServerHostRequestSchema = {
params: schema.object({ itemId: schema.string() }),
body: schema.object({
name: schema.maybe(schema.string()),
host_urls: schema.maybe(schema.arrayOf(schema.string(), { minSize: 1 })),
is_default: schema.maybe(schema.boolean({ defaultValue: false })),
}),
};
export const GetAllFleetServerHostRequestSchema = {};

View file

@ -11,6 +11,7 @@ export * from './agent';
export * from './package_policy';
export * from './epm';
export * from './enrollment_api_key';
export * from './fleet_server_policy_config';
export * from './output';
export * from './preconfiguration';
export * from './settings';

View file

@ -0,0 +1,121 @@
/*
* 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 expect from '@kbn/expect';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { skipIfNoDockerRegistry } from '../../helpers';
import { setupFleetAndAgents } from '../agents/services';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
const supertest = getService('supertest');
const esArchiver = getService('esArchiver');
const kibanaServer = getService('kibanaServer');
describe('fleet_fleet_server_hosts_crud', async function () {
skipIfNoDockerRegistry(providerContext);
before(async () => {
await esArchiver.load('x-pack/test/functional/es_archives/fleet/empty_fleet_server');
await kibanaServer.savedObjects.cleanStandardList();
});
setupFleetAndAgents(providerContext);
let defaultFleetServerHostId: string;
before(async function () {
await kibanaServer.savedObjects.clean({
types: ['fleet-fleet-server-host'],
});
const { body: defaultRes } = await supertest
.post(`/api/fleet/fleet_server_hosts`)
.set('kbn-xsrf', 'xxxx')
.send({
id: 'test-default-123',
name: 'Default',
is_default: true,
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
})
.expect(200);
await supertest
.post(`/api/fleet/fleet_server_hosts`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'Test',
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
})
.expect(200);
defaultFleetServerHostId = defaultRes.item.id;
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await esArchiver.unload('x-pack/test/functional/es_archives/fleet/empty_fleet_server');
});
describe('GET /fleet_server_hosts', () => {
it('should list the fleet server hosts', async () => {
const { body: res } = await supertest.get(`/api/fleet/fleet_server_hosts`).expect(200);
expect(res.items.length).to.be(2);
});
});
describe('GET /fleet_server_hosts/{itemId}', () => {
it('should return the requested fleet server host', async () => {
const { body: fleetServerHost } = await supertest
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
.expect(200);
expect(fleetServerHost).to.eql({
item: {
id: 'test-default-123',
name: 'Default',
is_default: true,
host_urls: ['https://test.fr:8080', 'https://test.fr:8081'],
is_preconfigured: false,
},
});
});
it('should return a 404 when retrieving a non existing fleet server host', async function () {
await supertest.get(`/api/fleet/fleet_server_hosts/idonotexists`).expect(404);
});
});
describe('PUT /fleet_server_hosts/{itemId}', () => {
it('should allow to update an existing fleet server host', async function () {
await supertest
.put(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'Default updated',
})
.expect(200);
const {
body: { item: fleetServerHost },
} = await supertest
.get(`/api/fleet/fleet_server_hosts/${defaultFleetServerHostId}`)
.expect(200);
expect(fleetServerHost.name).to.eql('Default updated');
});
it('should return a 404 when updating a non existing fleet server host', async function () {
await supertest
.put(`/api/fleet/fleet_server_hosts/idonotexists`)
.set('kbn-xsrf', 'xxxx')
.send({
name: 'new host1',
})
.expect(404);
});
});
});
}

View file

@ -58,5 +58,8 @@ export default function ({ loadTestFile, getService }) {
// Integrations
loadTestFile(require.resolve('./integrations'));
// Fleet server hosts
loadTestFile(require.resolve('./fleet_server_hosts/crud'));
});
}