[Fleet] Allow to configure proxy in kibana config file (#145737)

This commit is contained in:
Nicolas Chaulet 2022-11-21 11:06:29 -05:00 committed by GitHub
parent 88be956d3d
commit c1f1de73a7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 2 deletions

View file

@ -7,7 +7,7 @@
import React, { useMemo } from 'react';
import styled from 'styled-components';
import { EuiBasicTable, EuiButtonIcon, EuiFlexGroup, EuiFlexItem } from '@elastic/eui';
import { EuiBasicTable, EuiButtonIcon, EuiFlexGroup, EuiFlexItem, EuiIconTip } from '@elastic/eui';
import type { EuiBasicTableColumn } from '@elastic/eui';
import { i18n } from '@kbn/i18n';
@ -43,6 +43,19 @@ export const FleetProxiesTable: React.FunctionComponent<FleetProxiesTableProps>
{fleetProxy.name}
</p>
</NameFlexItemWithMaxWidth>
{fleetProxy.is_preconfigured && (
<EuiFlexItem grow={false}>
<EuiIconTip
content={i18n.translate('xpack.fleet.settings.fleetProxiesTable.managedTooltip', {
defaultMessage:
'This proxy is managed outside of Fleet. Please refer to your kibana config file for more info.',
})}
type="lock"
size="m"
color="subdued"
/>
</EuiFlexItem>
)}
</EuiFlexGroup>
),
width: '288px',
@ -60,7 +73,7 @@ export const FleetProxiesTable: React.FunctionComponent<FleetProxiesTableProps>
{
width: '68px',
render: (fleetProxy: FleetProxy) => {
const isDeleteVisible = true;
const isDeleteVisible = !fleetProxy.is_preconfigured;
return (
<EuiFlexGroup gutterSize="s" justifyContent="flexEnd">

View file

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

View file

@ -0,0 +1,177 @@
/*
* 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 { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/server';
import { isEqual } from 'lodash';
import pMap from 'p-map';
import type { FleetConfigType } from '../../config';
import type { FleetProxy } from '../../types';
import {
bulkGetFleetProxies,
createFleetProxy,
deleteFleetProxy,
listFleetProxies,
updateFleetProxy,
} from '../fleet_proxies';
import { listFleetServerHostsForProxyId } from '../fleet_server_host';
import { agentPolicyService } from '../agent_policy';
import { outputService } from '../output';
export function getPreconfiguredFleetProxiesFromConfig(config?: FleetConfigType) {
const { proxies: fleetProxiesFromConfig } = config;
return fleetProxiesFromConfig.map((proxyConfig: any) => ({
...proxyConfig,
is_preconfigured: true,
}));
}
function hasChanged(existingProxy: FleetProxy, preconfiguredFleetProxy: FleetProxy) {
return (
(!existingProxy.is_preconfigured ||
existingProxy.name !== existingProxy.name ||
existingProxy.url !== preconfiguredFleetProxy.name ||
!isEqual(
existingProxy.proxy_headers ?? null,
preconfiguredFleetProxy.proxy_headers ?? null
) ||
existingProxy.certificate_authorities) ??
null !== preconfiguredFleetProxy.certificate_authorities ??
(null || existingProxy.certificate) ??
null !== preconfiguredFleetProxy.certificate ??
(null || existingProxy.certificate_key) ??
null !== preconfiguredFleetProxy.certificate_key ??
null
);
}
async function createOrUpdatePreconfiguredFleetProxies(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
preconfiguredFleetProxies: FleetProxy[]
) {
const existingFleetProxies = await bulkGetFleetProxies(
soClient,
preconfiguredFleetProxies.map(({ id }) => id),
{ ignoreNotFound: true }
);
await Promise.all(
preconfiguredFleetProxies.map(async (preconfiguredFleetProxy) => {
const existingProxy = existingFleetProxies.find(
(fleetProxy) => fleetProxy.id === preconfiguredFleetProxy.id
);
const { id, ...data } = preconfiguredFleetProxy;
const isCreate = !existingProxy;
const isUpdateWithNewData = existingProxy
? hasChanged(existingProxy, preconfiguredFleetProxy)
: false;
if (isCreate) {
await createFleetProxy(
soClient,
{
...data,
is_preconfigured: true,
},
{ id, overwrite: true, fromPreconfiguration: true }
);
} else if (isUpdateWithNewData) {
await updateFleetProxy(
soClient,
id,
{
...data,
is_preconfigured: true,
},
{ fromPreconfiguration: true }
);
// Bump all the agent policy that use that proxy
const [{ items: fleetServerHosts }, { items: outputs }] = await Promise.all([
listFleetServerHostsForProxyId(soClient, id),
outputService.listAllForProxyId(soClient, id),
]);
if (
fleetServerHosts.some((host) => host.is_default) ||
outputs.some((output) => output.is_default || output.is_default_monitoring)
) {
await agentPolicyService.bumpAllAgentPolicies(soClient, esClient);
} else {
await pMap(
outputs,
(output) =>
agentPolicyService.bumpAllAgentPoliciesForOutput(soClient, esClient, output.id),
{
concurrency: 20,
}
);
await pMap(
fleetServerHosts,
(fleetServerHost) =>
agentPolicyService.bumpAllAgentPoliciesForFleetServerHosts(
soClient,
esClient,
fleetServerHost.id
),
{
concurrency: 20,
}
);
}
}
})
);
}
async function cleanPreconfiguredFleetProxies(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
preconfiguredFleetProxies: FleetProxy[]
) {
const existingFleetProxies = await listFleetProxies(soClient);
const existingPreconfiguredFleetProxies = existingFleetProxies.items.filter(
(o) => o.is_preconfigured === true
);
for (const existingFleetProxy of existingPreconfiguredFleetProxies) {
const hasBeenDelete = !preconfiguredFleetProxies.find(({ id }) => existingFleetProxy.id === id);
if (!hasBeenDelete) {
continue;
}
const [{ items: fleetServerHosts }, { items: outputs }] = await Promise.all([
listFleetServerHostsForProxyId(soClient, existingFleetProxy.id),
outputService.listAllForProxyId(soClient, existingFleetProxy.id),
]);
const isUsed = fleetServerHosts.length > 0 || outputs.length > 0;
if (isUsed) {
await updateFleetProxy(
soClient,
existingFleetProxy.id,
{ is_preconfigured: false },
{
fromPreconfiguration: true,
}
);
} else {
await deleteFleetProxy(soClient, existingFleetProxy.id, {
fromPreconfiguration: true,
});
}
}
}
export async function ensurePreconfiguredFleetProxies(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
preconfiguredFleetProxies: FleetProxy[]
) {
await createOrUpdatePreconfiguredFleetProxies(soClient, esClient, preconfiguredFleetProxies);
await cleanPreconfiguredFleetProxies(soClient, esClient, preconfiguredFleetProxies);
}

View file

@ -19,6 +19,7 @@ import { setupFleet } from './setup';
jest.mock('./preconfiguration');
jest.mock('./preconfiguration/outputs');
jest.mock('./preconfiguration/fleet_proxies');
jest.mock('./settings');
jest.mock('./output');
jest.mock('./download_source');

View file

@ -29,6 +29,10 @@ import {
ensurePreconfiguredOutputs,
getPreconfiguredOutputFromConfig,
} from './preconfiguration/outputs';
import {
ensurePreconfiguredFleetProxies,
getPreconfiguredFleetProxiesFromConfig,
} from './preconfiguration/fleet_proxies';
import { outputService } from './output';
import { downloadSourceService } from './download_source';
@ -86,6 +90,13 @@ async function createSetupSideEffects(
await migrateSettingsToFleetServerHost(soClient);
logger.debug('Setting up Fleet download source');
const defaultDownloadSource = await downloadSourceService.ensureDefault(soClient);
// Need to be done before outputs and fleet server hosts as these object can reference a proxy
logger.debug('Setting up Proxy');
await ensurePreconfiguredFleetProxies(
soClient,
esClient,
getPreconfiguredFleetProxiesFromConfig(appContextService.getConfig())
);
logger.debug('Setting up Fleet Sever Hosts');
await ensurePreconfiguredFleetServerHosts(

View file

@ -95,6 +95,24 @@ export const PreconfiguredFleetServerHostsSchema = schema.arrayOf(
{ defaultValue: [] }
);
export const PreconfiguredFleetProxiesSchema = schema.arrayOf(
schema.object({
id: schema.string(),
name: schema.string(),
url: schema.string(),
proxy_headers: schema.maybe(
schema.recordOf(
schema.string(),
schema.oneOf([schema.string(), schema.boolean(), schema.number()])
)
),
certificate_authorities: schema.maybe(schema.string()),
certificate: schema.maybe(schema.string()),
certificate_key: schema.maybe(schema.string()),
}),
{ defaultValue: [] }
);
export const PreconfiguredAgentPoliciesSchema = schema.arrayOf(
schema.object({
...AgentPolicyBaseSchema,