mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
[Fleet] Restrict output type for Fleet Server (#149873)
This commit is contained in:
parent
4d353f0876
commit
e2e58635a3
11 changed files with 269 additions and 54 deletions
|
@ -56,3 +56,5 @@ export {
|
|||
isPackagePrerelease,
|
||||
mapPackageReleaseToIntegrationCardRelease,
|
||||
} from './package_prerelease';
|
||||
|
||||
export { getAllowedOutputTypeForPolicy } from './output_helpers';
|
||||
|
|
47
x-pack/plugins/fleet/common/services/output_helpers.test.ts
Normal file
47
x-pack/plugins/fleet/common/services/output_helpers.test.ts
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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 { getAllowedOutputTypeForPolicy } from './output_helpers';
|
||||
|
||||
describe('getAllowedOutputTypeForPolicy', () => {
|
||||
it('should return all available output type for an agent policy without APM and Fleet Server', () => {
|
||||
const res = getAllowedOutputTypeForPolicy({
|
||||
package_policies: [
|
||||
{
|
||||
package: { name: 'nginx' },
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
expect(res).toContain('elasticsearch');
|
||||
expect(res).toContain('logstash');
|
||||
});
|
||||
|
||||
it('should return only elasticsearch for an agent policy with APM', () => {
|
||||
const res = getAllowedOutputTypeForPolicy({
|
||||
package_policies: [
|
||||
{
|
||||
package: { name: 'apm' },
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
expect(res).toEqual(['elasticsearch']);
|
||||
});
|
||||
|
||||
it('should return only elasticsearch for an agent policy with Fleet Server', () => {
|
||||
const res = getAllowedOutputTypeForPolicy({
|
||||
package_policies: [
|
||||
{
|
||||
package: { name: 'fleet_server' },
|
||||
},
|
||||
],
|
||||
} as any);
|
||||
|
||||
expect(res).toEqual(['elasticsearch']);
|
||||
});
|
||||
});
|
27
x-pack/plugins/fleet/common/services/output_helpers.ts
Normal file
27
x-pack/plugins/fleet/common/services/output_helpers.ts
Normal file
|
@ -0,0 +1,27 @@
|
|||
/*
|
||||
* 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 { AgentPolicy } from '../types';
|
||||
import { FLEET_APM_PACKAGE, FLEET_SERVER_PACKAGE, outputType } from '../constants';
|
||||
|
||||
/**
|
||||
* Return allowed output type for a given agent policy,
|
||||
* Fleet Server and APM cannot use anything else than same cluster ES
|
||||
*/
|
||||
export function getAllowedOutputTypeForPolicy(agentPolicy: AgentPolicy) {
|
||||
const isRestrictedToSameClusterES =
|
||||
agentPolicy.package_policies &&
|
||||
agentPolicy.package_policies.some(
|
||||
(p) => p.package?.name === FLEET_APM_PACKAGE || p.package?.name === FLEET_SERVER_PACKAGE
|
||||
);
|
||||
|
||||
if (isRestrictedToSameClusterES) {
|
||||
return [outputType.Elasticsearch];
|
||||
}
|
||||
|
||||
return Object.values(outputType);
|
||||
}
|
|
@ -111,23 +111,89 @@ describe('useOutputOptions', () => {
|
|||
expect(result.current.dataOutputOptions).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"disabled": false,
|
||||
"disabled": undefined,
|
||||
"inputDisplay": "Default (currently Output 1)",
|
||||
"value": "@@##DEFAULT_SELECT##@@",
|
||||
},
|
||||
Object {
|
||||
"disabled": false,
|
||||
"inputDisplay": "Output 1",
|
||||
"disabled": true,
|
||||
"inputDisplay": <React.Fragment>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
Output 1
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"value": "output1",
|
||||
},
|
||||
Object {
|
||||
"disabled": false,
|
||||
"inputDisplay": "Output 2",
|
||||
"disabled": true,
|
||||
"inputDisplay": <React.Fragment>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
Output 2
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"value": "output2",
|
||||
},
|
||||
Object {
|
||||
"disabled": false,
|
||||
"inputDisplay": "Output 3",
|
||||
"disabled": true,
|
||||
"inputDisplay": <React.Fragment>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
Output 3
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"value": "output3",
|
||||
},
|
||||
]
|
||||
|
@ -173,23 +239,89 @@ describe('useOutputOptions', () => {
|
|||
expect(result.current.dataOutputOptions).toMatchInlineSnapshot(`
|
||||
Array [
|
||||
Object {
|
||||
"disabled": false,
|
||||
"disabled": undefined,
|
||||
"inputDisplay": "Default (currently Output 1)",
|
||||
"value": "@@##DEFAULT_SELECT##@@",
|
||||
},
|
||||
Object {
|
||||
"disabled": true,
|
||||
"inputDisplay": "Output 1",
|
||||
"inputDisplay": <React.Fragment>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
Output 1
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"value": "output1",
|
||||
},
|
||||
Object {
|
||||
"disabled": true,
|
||||
"inputDisplay": "Output 2",
|
||||
"inputDisplay": <React.Fragment>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
Output 2
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"value": "output2",
|
||||
},
|
||||
Object {
|
||||
"disabled": true,
|
||||
"inputDisplay": "Output 3",
|
||||
"inputDisplay": <React.Fragment>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
Output 3
|
||||
</EuiText>
|
||||
<EuiSpacer
|
||||
size="xs"
|
||||
/>
|
||||
<EuiText
|
||||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": undefined,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
"value": "output3",
|
||||
},
|
||||
]
|
||||
|
@ -309,9 +441,13 @@ describe('useOutputOptions', () => {
|
|||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Logstash output for agent integration is not supported for APM"
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText"
|
||||
values={Object {}}
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisableOutputTypeText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": "logstash",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
|
@ -337,9 +473,13 @@ describe('useOutputOptions', () => {
|
|||
size="s"
|
||||
>
|
||||
<FormattedMessage
|
||||
defaultMessage="Logstash output for agent integration is not supported for APM"
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText"
|
||||
values={Object {}}
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
values={
|
||||
Object {
|
||||
"outputType": "logstash",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</EuiText>
|
||||
</React.Fragment>,
|
||||
|
|
|
@ -16,11 +16,8 @@ import {
|
|||
useGetDownloadSources,
|
||||
useGetFleetServerHosts,
|
||||
} from '../../../../hooks';
|
||||
import {
|
||||
LICENCE_FOR_PER_POLICY_OUTPUT,
|
||||
FLEET_APM_PACKAGE,
|
||||
outputType,
|
||||
} from '../../../../../../../common/constants';
|
||||
import { LICENCE_FOR_PER_POLICY_OUTPUT } from '../../../../../../../common/constants';
|
||||
import { getAllowedOutputTypeForPolicy } from '../../../../../../../common/services';
|
||||
import type { NewAgentPolicy, AgentPolicy } from '../../../../types';
|
||||
|
||||
// The super select component do not support null or '' as a value
|
||||
|
@ -63,11 +60,10 @@ export function useOutputOptions(agentPolicy: Partial<NewAgentPolicy | AgentPoli
|
|||
const licenseService = useLicense();
|
||||
|
||||
const isLicenceAllowingPolicyPerOutput = licenseService.hasAtLeast(LICENCE_FOR_PER_POLICY_OUTPUT);
|
||||
const isAgentPolicyUsingAPM =
|
||||
'package_policies' in agentPolicy &&
|
||||
agentPolicy.package_policies?.some((packagePolicy) => {
|
||||
return typeof packagePolicy !== 'string' && packagePolicy.package?.name === FLEET_APM_PACKAGE;
|
||||
});
|
||||
const allowedOutputTypes = useMemo(
|
||||
() => getAllowedOutputTypeForPolicy(agentPolicy as AgentPolicy),
|
||||
[agentPolicy]
|
||||
);
|
||||
|
||||
const dataOutputOptions = useMemo(() => {
|
||||
if (outputsRequest.isLoading || !outputsRequest.data) {
|
||||
|
@ -81,36 +77,42 @@ export function useOutputOptions(agentPolicy: Partial<NewAgentPolicy | AgentPoli
|
|||
const defaultOutput = outputsRequest.data.items.find((item) => item.is_default);
|
||||
const defaultOutputName = defaultOutput?.name;
|
||||
const defaultOutputDisabled =
|
||||
isAgentPolicyUsingAPM && defaultOutput?.type === outputType.Logstash;
|
||||
defaultOutput?.type && !allowedOutputTypes.includes(defaultOutput.type);
|
||||
|
||||
const defaultOutputDisabledMessage = defaultOutputDisabled ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText"
|
||||
defaultMessage="Logstash output for agent integration is not supported for APM"
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisableOutputTypeText"
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
values={{
|
||||
outputType: defaultOutput.type,
|
||||
}}
|
||||
/>
|
||||
) : undefined;
|
||||
|
||||
return [
|
||||
getDefaultOutput(defaultOutputName, defaultOutputDisabled, defaultOutputDisabledMessage),
|
||||
...outputsRequest.data.items.map((item) => {
|
||||
const isLogstashOutputWithAPM = isAgentPolicyUsingAPM && item.type === outputType.Logstash;
|
||||
const isOutputTypeUnsupported = !allowedOutputTypes.includes(item.type);
|
||||
|
||||
return {
|
||||
value: item.id,
|
||||
inputDisplay: getOutputLabel(
|
||||
item.name,
|
||||
isLogstashOutputWithAPM ? (
|
||||
isOutputTypeUnsupported ? (
|
||||
<FormattedMessage
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText"
|
||||
defaultMessage="Logstash output for agent integration is not supported for APM"
|
||||
id="xpack.fleet.agentPolicyForm.outputOptionDisabledTypeNotSupportedText"
|
||||
defaultMessage="{outputType} output for agent integration is not supported for Fleet Server or APM."
|
||||
values={{
|
||||
outputType: item.type,
|
||||
}}
|
||||
/>
|
||||
) : undefined
|
||||
),
|
||||
disabled: !isLicenceAllowingPolicyPerOutput || isLogstashOutputWithAPM,
|
||||
disabled: !isLicenceAllowingPolicyPerOutput || isOutputTypeUnsupported,
|
||||
};
|
||||
}),
|
||||
];
|
||||
}, [outputsRequest, isLicenceAllowingPolicyPerOutput, isAgentPolicyUsingAPM]);
|
||||
}, [outputsRequest, isLicenceAllowingPolicyPerOutput, allowedOutputTypes]);
|
||||
|
||||
const monitoringOutputOptions = useMemo(() => {
|
||||
if (outputsRequest.isLoading || !outputsRequest.data) {
|
||||
|
|
|
@ -190,7 +190,7 @@ describe('validateOutputForPolicy', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should not allow APM for a logstash output', async () => {
|
||||
it('should not allow logstash output to be used with a policy using fleet server or APM', async () => {
|
||||
mockHasLicence(true);
|
||||
mockedOutputService.get.mockResolvedValue({
|
||||
type: 'logstash',
|
||||
|
@ -203,12 +203,12 @@ describe('validateOutputForPolicy', () => {
|
|||
monitoring_output_id: 'test1',
|
||||
},
|
||||
{ data_output_id: 'newdataoutput', monitoring_output_id: 'test1' },
|
||||
true // hasAPM
|
||||
['elasticsearch']
|
||||
)
|
||||
).rejects.toThrow(/Logstash output is not usable with policy using the APM integration./);
|
||||
).rejects.toThrow(/logstash output is not usable with that policy./);
|
||||
});
|
||||
|
||||
it('should allow APM for an elasticsearch output', async () => {
|
||||
it('should allow elasticsearch output to be used with a policy using fleet server or APM', async () => {
|
||||
mockHasLicence(true);
|
||||
mockedOutputService.get.mockResolvedValue({
|
||||
type: 'elasticsearch',
|
||||
|
@ -221,7 +221,7 @@ describe('validateOutputForPolicy', () => {
|
|||
monitoring_output_id: 'test1',
|
||||
},
|
||||
{ data_output_id: 'newdataoutput', monitoring_output_id: 'test1' },
|
||||
true // hasAPM
|
||||
['elasticsearch']
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -238,7 +238,7 @@ describe('validateOutputForPolicy', () => {
|
|||
monitoring_output_id: 'test1',
|
||||
},
|
||||
{ data_output_id: 'newdataoutput', monitoring_output_id: 'test1' },
|
||||
false // do not have APM
|
||||
['logstash', 'elasticsearch']
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -42,7 +42,7 @@ export async function validateOutputForPolicy(
|
|||
soClient: SavedObjectsClientContract,
|
||||
newData: Partial<AgentPolicySOAttributes>,
|
||||
existingData: Partial<AgentPolicySOAttributes> = {},
|
||||
isPolicyUsingAPM = false
|
||||
allowedOutputTypeForPolicy = Object.values(outputType)
|
||||
) {
|
||||
if (
|
||||
newData.data_output_id === existingData.data_output_id &&
|
||||
|
@ -53,13 +53,13 @@ export async function validateOutputForPolicy(
|
|||
|
||||
const data = { ...existingData, ...newData };
|
||||
|
||||
if (isPolicyUsingAPM) {
|
||||
const dataOutput = await getDataOutputForAgentPolicy(soClient, data);
|
||||
const isOutputTypeRestricted =
|
||||
allowedOutputTypeForPolicy.length !== Object.values(outputType).length;
|
||||
|
||||
if (dataOutput.type === outputType.Logstash) {
|
||||
throw new OutputInvalidError(
|
||||
'Logstash output is not usable with policy using the APM integration.'
|
||||
);
|
||||
if (isOutputTypeRestricted) {
|
||||
const dataOutput = await getDataOutputForAgentPolicy(soClient, data);
|
||||
if (!allowedOutputTypeForPolicy.includes(dataOutput.type)) {
|
||||
throw new OutputInvalidError(`${dataOutput.type} output is not usable with that policy.`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -38,7 +38,7 @@ import type {
|
|||
ListWithKuery,
|
||||
NewPackagePolicy,
|
||||
} from '../types';
|
||||
import { packageToPackagePolicy } from '../../common/services';
|
||||
import { getAllowedOutputTypeForPolicy, packageToPackagePolicy } from '../../common/services';
|
||||
import {
|
||||
agentPolicyStatuses,
|
||||
AGENT_POLICY_INDEX,
|
||||
|
@ -122,7 +122,7 @@ class AgentPolicyService {
|
|||
soClient,
|
||||
agentPolicy,
|
||||
existingAgentPolicy,
|
||||
this.hasAPMIntegration(existingAgentPolicy)
|
||||
getAllowedOutputTypeForPolicy(existingAgentPolicy)
|
||||
);
|
||||
await soClient.update<AgentPolicySOAttributes>(SAVED_OBJECT_TYPE, id, {
|
||||
...agentPolicy,
|
||||
|
|
|
@ -13732,7 +13732,6 @@
|
|||
"xpack.fleet.agentPolicyForm.nameSpaceFieldDescription.fleetUserGuideLabel": "En savoir plus",
|
||||
"xpack.fleet.agentPolicyForm.namespaceFieldLabel": "Espace de nom par défaut",
|
||||
"xpack.fleet.agentPolicyForm.newAgentPolicyFieldLabel": "Nouveau nom de la stratégie d'agent",
|
||||
"xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText": "La sortie Logstash pour l'intégration des agents n'est pas prise en charge pour APM",
|
||||
"xpack.fleet.agentPolicyForm.systemMonitoringText": "Collecte des logs et des mesures du système",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription": "Délai d'expiration facultatif en secondes. Si une valeur est renseignée, un agent est automatiquement désenregistré après une période d'inactivité équivalente à ce délai.",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel": "Délai d'expiration pour le désenregistrement",
|
||||
|
|
|
@ -13719,7 +13719,6 @@
|
|||
"xpack.fleet.agentPolicyForm.nameSpaceFieldDescription.fleetUserGuideLabel": "詳細",
|
||||
"xpack.fleet.agentPolicyForm.namespaceFieldLabel": "デフォルト名前空間",
|
||||
"xpack.fleet.agentPolicyForm.newAgentPolicyFieldLabel": "新しいエージェントポリシー名",
|
||||
"xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText": "APMではエージェント統合のLogstash出力はサポートされていません",
|
||||
"xpack.fleet.agentPolicyForm.systemMonitoringText": "システムログとメトリックの収集",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription": "任意のタイムアウト(秒)。指定されている場合、この期間が経過した後、エージェントは自動的に登録解除されます。",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel": "登録解除タイムアウト",
|
||||
|
|
|
@ -13737,7 +13737,6 @@
|
|||
"xpack.fleet.agentPolicyForm.nameSpaceFieldDescription.fleetUserGuideLabel": "了解详情",
|
||||
"xpack.fleet.agentPolicyForm.namespaceFieldLabel": "默认命名空间",
|
||||
"xpack.fleet.agentPolicyForm.newAgentPolicyFieldLabel": "新代理策略名称",
|
||||
"xpack.fleet.agentPolicyForm.outputOptionDisabledAPMAndLogstashText": "APM 不支持代理集成的 Logstash 输出",
|
||||
"xpack.fleet.agentPolicyForm.systemMonitoringText": "收集系统日志和指标",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutDescription": "可选超时(秒)。若提供,代理断开连接此段时间后,将自动注销。",
|
||||
"xpack.fleet.agentPolicyForm.unenrollmentTimeoutLabel": "注销超时",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue