[Fleet] Restrict output type for Fleet Server (#149873)

This commit is contained in:
Nicolas Chaulet 2023-01-30 17:58:30 -04:00 committed by GitHub
parent 4d353f0876
commit e2e58635a3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 269 additions and 54 deletions

View file

@ -56,3 +56,5 @@ export {
isPackagePrerelease,
mapPackageReleaseToIntegrationCardRelease,
} from './package_prerelease';
export { getAllowedOutputTypeForPolicy } from './output_helpers';

View 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']);
});
});

View 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);
}

View file

@ -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>,

View file

@ -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) {

View file

@ -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']
);
});
});

View file

@ -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.`);
}
}

View file

@ -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,

View file

@ -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",

View file

@ -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": "登録解除タイムアウト",

View file

@ -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": "注销超时",