Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] added output validation when creating package policy #175985

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 18 additions & 5 deletions x-pack/plugins/fleet/common/services/output_helpers.ts
Expand Up @@ -19,18 +19,21 @@ import {
RESERVED_CONFIG_YML_KEYS,
} from '../constants';

const sameClusterRestrictedPackages = [
FLEET_SERVER_PACKAGE,
FLEET_SYNTHETICS_PACKAGE,
FLEET_APM_PACKAGE,
];

/**
* 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) {
export function getAllowedOutputTypeForPolicy(agentPolicy: AgentPolicy): string[] {
const isRestrictedToSameClusterES =
agentPolicy.package_policies &&
agentPolicy.package_policies.some(
(p) =>
p.package?.name === FLEET_SERVER_PACKAGE ||
p.package?.name === FLEET_SYNTHETICS_PACKAGE ||
p.package?.name === FLEET_APM_PACKAGE
(p) => p.package?.name && sameClusterRestrictedPackages.includes(p.package?.name)
);

if (isRestrictedToSameClusterES) {
Expand All @@ -40,6 +43,16 @@ export function getAllowedOutputTypeForPolicy(agentPolicy: AgentPolicy) {
return Object.values(outputType);
}

export function getAllowedOutputTypesForIntegration(packageName: string): string[] {
const isRestrictedToSameClusterES = sameClusterRestrictedPackages.includes(packageName);

if (isRestrictedToSameClusterES) {
return [outputType.Elasticsearch];
}

return Object.values(outputType);
}

export function outputYmlIncludesReservedPerformanceKey(
configYml: string,
// Dependency injection for `safeLoad` prevents bundle size issues 🤷‍♀️
Expand Down
Expand Up @@ -13,6 +13,7 @@ import { appContextService } from '..';
import { outputService } from '../output';

import { validateOutputForPolicy } from '.';
import { validateOutputForNewPackagePolicy } from './outputs_helpers';

jest.mock('../app_context');
jest.mock('../output');
Expand Down Expand Up @@ -252,3 +253,94 @@ describe('validateOutputForPolicy', () => {
});
});
});

describe('validateOutputForNewPackagePolicy', () => {
it('should not allow fleet_server integration to be added to a policy using a logstash output', async () => {
mockHasLicence(true);
mockedOutputService.get.mockResolvedValue({
type: 'logstash',
} as any);
await expect(
validateOutputForNewPackagePolicy(
savedObjectsClientMock.create(),
{
name: 'Agent policy',
data_output_id: 'test1',
monitoring_output_id: 'test1',
} as any,
'fleet_server'
)
).rejects.toThrow(
'Integration "fleet_server" cannot be added to agent policy "Agent policy" because it uses output type "logstash".'
);
});

it('should not allow apm integration to be added to a policy using a kafka output', async () => {
mockHasLicence(true);
mockedOutputService.get.mockResolvedValue({
type: 'kafka',
} as any);
await expect(
validateOutputForNewPackagePolicy(
savedObjectsClientMock.create(),
{
name: 'Agent policy',
data_output_id: 'test1',
monitoring_output_id: 'test1',
} as any,
'apm'
)
).rejects.toThrow(
'Integration "apm" cannot be added to agent policy "Agent policy" because it uses output type "kafka".'
);
});

it('should not allow synthetics integration to be added to a policy using a default logstash output', async () => {
mockHasLicence(true);
mockedOutputService.get.mockResolvedValue({
type: 'logstash',
} as any);
mockedOutputService.getDefaultDataOutputId.mockResolvedValue('default');
await expect(
validateOutputForNewPackagePolicy(
savedObjectsClientMock.create(),
{
name: 'Agent policy',
} as any,
'synthetics'
)
).rejects.toThrow(
'Integration "synthetics" cannot be added to agent policy "Agent policy" because it uses output type "logstash".'
);
});

it('should allow other integration to be added to a policy using logstash output', async () => {
mockHasLicence(true);
mockedOutputService.get.mockResolvedValue({
type: 'logstash',
} as any);

await validateOutputForNewPackagePolicy(
savedObjectsClientMock.create(),
{
name: 'Agent policy',
} as any,
'nginx'
);
});

it('should allow fleet_server integration to be added to a policy using elasticsearch output', async () => {
mockHasLicence(true);
mockedOutputService.get.mockResolvedValue({
type: 'elasticsearch',
} as any);

await validateOutputForNewPackagePolicy(
savedObjectsClientMock.create(),
{
name: 'Agent policy',
} as any,
'fleet_server'
);
});
});
Expand Up @@ -7,6 +7,8 @@

import type { SavedObjectsClientContract } from '@kbn/core/server';

import { getAllowedOutputTypesForIntegration } from '../../../common/services/output_helpers';

import type { AgentPolicySOAttributes, AgentPolicy } from '../../types';
import { LICENCE_FOR_PER_POLICY_OUTPUT, outputType } from '../../../common/constants';
import { policyHasFleetServer, policyHasSyntheticsIntegration } from '../../../common/services';
Expand Down Expand Up @@ -46,7 +48,7 @@ export async function validateOutputForPolicy(
soClient: SavedObjectsClientContract,
newData: Partial<AgentPolicySOAttributes>,
existingData: Partial<AgentPolicySOAttributes> = {},
allowedOutputTypeForPolicy = Object.values(outputType)
allowedOutputTypeForPolicy: string[] = Object.values(outputType)
) {
if (
newData.data_output_id === existingData.data_output_id &&
Expand Down Expand Up @@ -93,3 +95,23 @@ export async function validateOutputForPolicy(
);
}
}

export async function validateOutputForNewPackagePolicy(
soClient: SavedObjectsClientContract,
agentPolicy: AgentPolicy,
packageName: string
) {
const allowedOutputTypeForPolicy = getAllowedOutputTypesForIntegration(packageName);

const isOutputTypeRestricted =
allowedOutputTypeForPolicy.length !== Object.values(outputType).length;

if (isOutputTypeRestricted) {
const dataOutput = await getDataOutputForAgentPolicy(soClient, agentPolicy);
if (!allowedOutputTypeForPolicy.includes(dataOutput.type)) {
throw new OutputInvalidError(
`Integration "${packageName}" cannot be added to agent policy "${agentPolicy.name}" because it uses output type "${dataOutput.type}".`
);
}
}
}
15 changes: 7 additions & 8 deletions x-pack/plugins/fleet/server/services/package_policy.ts
Expand Up @@ -46,8 +46,6 @@ import {
} from '../../common/services';
import {
SO_SEARCH_LIMIT,
FLEET_APM_PACKAGE,
outputType,
PACKAGES_SAVED_OBJECT_TYPE,
DATASET_VAR_NAME,
} from '../../common/constants';
Expand Down Expand Up @@ -103,7 +101,6 @@ import { getAuthzFromRequest, doesNotHaveRequiredFleetAuthz } from './security';

import { storedPackagePolicyToAgentInputs } from './agent_policies';
import { agentPolicyService } from './agent_policy';
import { getDataOutputForAgentPolicy } from './agent_policies';
import { getPackageInfo, getInstallation, ensureInstalledPackage } from './epm/packages';
import { getAssetsDataFromAssetsMap } from './epm/packages/assets';
import { compileTemplate } from './epm/agent/agent';
Expand All @@ -124,6 +121,7 @@ import {
isSecretStorageEnabled,
} from './secrets';
import { getPackageAssetsMap } from './epm/packages/get';
import { validateOutputForNewPackagePolicy } from './agent_policies/outputs_helpers';

export type InputsOverride = Partial<NewPackagePolicyInput> & {
vars?: Array<NewPackagePolicyInput['vars'] & { name: string }>;
Expand Down Expand Up @@ -225,11 +223,12 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
true
);

if (agentPolicy && enrichedPackagePolicy.package?.name === FLEET_APM_PACKAGE) {
const dataOutput = await getDataOutputForAgentPolicy(soClient, agentPolicy);
if (dataOutput.type === outputType.Logstash) {
throw new FleetError('You cannot add APM to a policy using a logstash output');
}
if (agentPolicy && enrichedPackagePolicy.package?.name) {
await validateOutputForNewPackagePolicy(
soClient,
agentPolicy,
enrichedPackagePolicy.package?.name
);
}
await validateIsNotHostedPolicy(soClient, enrichedPackagePolicy.policy_id, options?.force);

Expand Down