[Fleet] Additional datastreams permissions API (#210452)

This commit is contained in:
Nicolas Chaulet 2025-02-14 09:06:12 -05:00 committed by GitHub
parent f5182586cd
commit 6ecb66df7f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 2020 additions and 255 deletions

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -1737,6 +1737,7 @@
"properties": {}
},
"fleet-package-policies": {
"dynamic": false,
"properties": {
"bump_agent_policy_revision": {
"type": "boolean"
@ -2334,6 +2335,7 @@
}
},
"ingest-package-policies": {
"dynamic": false,
"properties": {
"bump_agent_policy_revision": {
"type": "boolean"

View file

@ -111,7 +111,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"fleet-agent-policies": "4a5c6477d2a61121e95ea9865ed1403a28c38706",
"fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417",
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
"fleet-package-policies": "8173220091e28ff4afa8238bb37749599378f9e5",
"fleet-package-policies": "b1ded996118af658bc420a737ff3c4d784641fc7",
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
"fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d",
"fleet-setup-lock": "0dc784792c79b5af5a6e6b5dcac06b0dbaa90bde",
@ -127,7 +127,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"ingest-agent-policies": "57ebfb047cf0b81c6fa0ceed8586fa7199c7c5e2",
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
"ingest-outputs": "6743521f501bd77b1523dbb1df48d7c47fdad529",
"ingest-package-policies": "870f8c21fe3602f31075430a1fdfb052c62d4a14",
"ingest-package-policies": "6a80000fdf2544f2485b0c6a51ecc434b6a12987",
"ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505",
"inventory-view": "fd2b7fe713956f261018dded00d8f8c986417763",
"kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad",

View file

@ -2,6 +2,7 @@
exports[`Fleet - validatePackagePolicy() works for packages with multiple policy templates (aka integrations) returns errors for invalid package policy 1`] = `
Object {
"additional_datastreams_permissions": null,
"description": null,
"inputs": Object {
"billing-aws/metrics": Object {

View file

@ -44,6 +44,10 @@ export const epmRouteService = {
return EPM_API_ROUTES.LIMITED_LIST_PATTERN;
},
getDatastreamsPath: () => {
return EPM_API_ROUTES.DATA_STREAMS_PATTERN;
},
getInfoPath: (pkgName: string, pkgVersion?: string) => {
if (pkgVersion) {
return EPM_API_ROUTES.INFO_PATTERN.replace('{pkgName}', pkgName).replace(

View file

@ -158,5 +158,21 @@ describe('toPackagePolicy', () => {
},
]);
});
it('should to pass additional_datastreams_permissions', () => {
const res = simplifiedPackagePolicytoNewPackagePolicy(
{
name: 'nginx-1',
namespace: 'default',
policy_id: 'policy123',
policy_ids: ['policy123'],
description: 'Test description',
additional_datastreams_permissions: ['logs-test-123'],
},
nginxPackageInfo as unknown as PackageInfo
);
expect(res.additional_datastreams_permissions).toEqual(['logs-test-123']);
});
});
});

View file

@ -51,6 +51,7 @@ export interface SimplifiedPackagePolicy {
vars?: SimplifiedVars;
inputs?: SimplifiedInputs;
supports_agentless?: boolean | null;
additional_datastreams_permissions?: string[];
}
export interface FormattedPackagePolicy extends Omit<PackagePolicy, 'inputs' | 'vars'> {
@ -161,6 +162,7 @@ export function simplifiedPackagePolicytoNewPackagePolicy(
inputs = {},
vars: packageLevelVars,
supports_agentless: supportsAgentless,
additional_datastreams_permissions: additionalDatastreamsPermissions,
} = data;
const packagePolicy = {
...packageToPackagePolicy(
@ -174,6 +176,10 @@ export function simplifiedPackagePolicytoNewPackagePolicy(
output_id: outputId,
};
if (additionalDatastreamsPermissions) {
packagePolicy.additional_datastreams_permissions = additionalDatastreamsPermissions;
}
if (packagePolicy.package && options?.experimental_data_stream_features) {
packagePolicy.package.experimental_data_stream_features =
options.experimental_data_stream_features;

View file

@ -347,6 +347,7 @@ describe('Fleet - validatePackagePolicy()', () => {
const noErrorsValidationResults = {
name: null,
additional_datastreams_permissions: null,
description: null,
namespace: null,
inputs: {
@ -395,6 +396,7 @@ describe('Fleet - validatePackagePolicy()', () => {
name: ['Name is required'],
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {
foo: {
vars: {
@ -461,6 +463,7 @@ describe('Fleet - validatePackagePolicy()', () => {
name: ['Name is required'],
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {
foo: {
vars: {
@ -514,6 +517,7 @@ describe('Fleet - validatePackagePolicy()', () => {
name: null,
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {},
vars: {},
});
@ -530,6 +534,7 @@ describe('Fleet - validatePackagePolicy()', () => {
name: null,
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {},
vars: {},
});
@ -549,6 +554,7 @@ describe('Fleet - validatePackagePolicy()', () => {
name: null,
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {},
vars: {},
});
@ -564,6 +570,7 @@ describe('Fleet - validatePackagePolicy()', () => {
).toEqual({
name: null,
description: null,
additional_datastreams_permissions: null,
namespace: null,
inputs: {},
vars: {},
@ -602,6 +609,7 @@ describe('Fleet - validatePackagePolicy()', () => {
name: null,
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {
foo: {
streams: {
@ -730,6 +738,7 @@ describe('Fleet - validatePackagePolicy()', () => {
)
).toEqual({
description: null,
additional_datastreams_permissions: null,
inputs: {
'linux/metrics': {
streams: {
@ -1150,6 +1159,7 @@ describe('Fleet - validationHasErrors()', () => {
validationHasErrors({
name: ['name error'],
description: null,
additional_datastreams_permissions: null,
namespace: null,
inputs: {
input1: {
@ -1159,10 +1169,12 @@ describe('Fleet - validationHasErrors()', () => {
},
})
).toBe(true);
expect(
validationHasErrors({
name: null,
description: null,
additional_datastreams_permissions: null,
namespace: null,
inputs: {
input1: {
@ -1176,6 +1188,7 @@ describe('Fleet - validationHasErrors()', () => {
validationHasErrors({
name: null,
description: null,
additional_datastreams_permissions: null,
namespace: null,
inputs: {
input1: {
@ -1193,6 +1206,7 @@ describe('Fleet - validationHasErrors()', () => {
name: null,
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {
input1: {
vars: { foo: null, bar: null },

View file

@ -54,6 +54,7 @@ export type PackagePolicyValidationResults = {
name: Errors;
description: Errors;
namespace: Errors;
additional_datastreams_permissions: Errors;
inputs: Record<PackagePolicyInput['type'], PackagePolicyInputValidationResults> | null;
} & PackagePolicyConfigValidationResults;
@ -109,6 +110,9 @@ const validatePackageRequiredVars = (
return hasMetRequiredCriteria ? null : evaluatedRequiredVars;
};
const VALIDATE_DATASTREAMS_PERMISSION_REGEX =
/^(logs)|(metrics)|(traces)|(synthetics)|(profiling)-(.*)$/;
/*
* Returns validation information for a given package policy and package info
* Note: this method assumes that `packagePolicy` is correctly structured for the given package
@ -124,6 +128,7 @@ export const validatePackagePolicy = (
name: null,
description: null,
namespace: null,
additional_datastreams_permissions: null,
inputs: {},
vars: {},
};
@ -146,6 +151,24 @@ export const validatePackagePolicy = (
}
}
if (packagePolicy?.additional_datastreams_permissions) {
validationResults.additional_datastreams_permissions =
packagePolicy?.additional_datastreams_permissions.reduce<null | string[]>(
(acc, additionalDatastreamsPermission) => {
if (!additionalDatastreamsPermission.match(VALIDATE_DATASTREAMS_PERMISSION_REGEX)) {
if (!acc) {
acc = [];
}
acc.push(
`${additionalDatastreamsPermission} is not valid, should match ${VALIDATE_DATASTREAMS_PERMISSION_REGEX.toString()}`
);
}
return acc;
},
null
);
}
// Validate package-level vars
const packageVarsByName = keyBy(packageInfo.vars || [], 'name');
const packageVars = Object.entries(packagePolicy.vars || {});

View file

@ -94,6 +94,7 @@ export interface NewPackagePolicy {
};
overrides?: { inputs?: { [key: string]: any } } | null;
supports_agentless?: boolean | null;
additional_datastreams_permissions?: string[];
}
export interface UpdatePackagePolicy extends NewPackagePolicy {

View file

@ -74,6 +74,7 @@ describe('StepDefinePackagePolicy', () => {
const validationResults = {
name: null,
description: null,
additional_datastreams_permissions: null,
namespace: null,
inputs: {},
vars: {

View file

@ -30,7 +30,11 @@ import type {
GetInputsTemplatesRequest,
GetInputsTemplatesResponse,
} from '../../types';
import type { FleetErrorResponse, GetStatsResponse } from '../../../common/types';
import type {
FleetErrorResponse,
GetEpmDataStreamsResponse,
GetStatsResponse,
} from '../../../common/types';
import { API_VERSIONS } from '../../../common/constants';
import { getCustomIntegrations } from '../../services/custom_integrations';
@ -240,6 +244,16 @@ export const useGetFileByPathQuery = (filePath: string) => {
);
};
export const useGetEpmDatastreams = () => {
return useQuery<GetEpmDataStreamsResponse, RequestError>(['get-epm-datastreams'], () =>
sendRequestForRq<GetEpmDataStreamsResponse>({
path: epmRouteService.getDatastreamsPath(),
method: 'get',
version: API_VERSIONS.public.v1,
})
);
};
export const sendGetFileByPath = (filePath: string) => {
return sendRequest<string>({
path: epmRouteService.getFilePath(filePath),

View file

@ -13,6 +13,7 @@ export const genericErrorResponse = () =>
statusCode: schema.maybe(schema.number()),
error: schema.maybe(schema.string()),
message: schema.string(),
attributes: schema.maybe(schema.any()),
},
{
meta: { description: 'Generic Error' },

View file

@ -637,6 +637,7 @@ export const getSavedObjectTypes = (
importableAndExportable: false,
},
mappings: {
dynamic: false,
properties: {
name: { type: 'keyword' },
description: { type: 'text' },
@ -844,6 +845,14 @@ export const getSavedObjectTypes = (
},
],
},
'18': {
changes: [
{
type: 'mappings_addition',
addedMappings: {}, // Empty to add dynamic:false
},
],
},
},
migrations: {
'7.10.0': migratePackagePolicyToV7100,
@ -871,6 +880,7 @@ export const getSavedObjectTypes = (
importableAndExportable: false,
},
mappings: {
dynamic: false,
properties: {
name: { type: 'keyword' },
description: { type: 'text' },
@ -937,6 +947,14 @@ export const getSavedObjectTypes = (
},
],
},
'4': {
changes: [
{
type: 'mappings_addition',
addedMappings: {}, // Empty to add dynamic:false
},
],
},
},
},
[PACKAGES_SAVED_OBJECT_TYPE]: {

View file

@ -409,6 +409,70 @@ describe('storedPackagePoliciesToAgentPermissions()', () => {
});
});
it('Add additional_datastream_permissions', async () => {
const packagePolicies: PackagePolicy[] = [
{
id: 'package-policy-uuid-test-123',
name: 'test-policy',
namespace: 'test',
enabled: true,
package: { name: 'test_package', version: '0.0.0', title: 'Test Package' },
inputs: [
{
type: 'test-logs',
enabled: true,
streams: [
{
id: 'test-logs',
enabled: true,
data_stream: { type: 'logs', dataset: 'some-logs' },
},
],
},
{
type: 'test-metrics',
enabled: false,
streams: [
{
id: 'test-logs',
enabled: false,
data_stream: { type: 'metrics', dataset: 'some-metrics' },
},
],
},
],
additional_datastreams_permissions: ['logs-test-default', 'metrics-test-default'],
created_at: '',
updated_at: '',
created_by: '',
updated_by: '',
revision: 1,
policy_id: '',
policy_ids: [''],
},
];
const permissions = await storedPackagePoliciesToAgentPermissions(
packageInfoCache,
'test',
packagePolicies
);
expect(permissions).toMatchObject({
'package-policy-uuid-test-123': {
indices: [
{
names: ['logs-some-logs-test'],
privileges: ['auto_configure', 'create_doc'],
},
{
names: ['logs-test-default', 'metrics-test-default'],
privileges: ['auto_configure', 'create_doc'],
},
],
},
});
});
it('Returns the dataset for the compiled data_streams', async () => {
const packagePolicies: PackagePolicy[] = [
{

View file

@ -106,7 +106,7 @@ export function storedPackagePoliciesToAgentPermissions(
const dataStreams = getNormalizedDataStreams(pkg);
if (!dataStreams || dataStreams.length === 0) {
return [packagePolicy.name, undefined];
return [packagePolicy.id, maybeAddAdditionalPackagePoliciesPermissions(packagePolicy)];
}
let dataStreamsForPermissions: DataStreamMeta[];
@ -183,10 +183,17 @@ export function storedPackagePoliciesToAgentPermissions(
}
// namespace is either the package policy's or the agent policy one
const namespace = packagePolicy?.namespace || agentPolicyNamespace;
return maybeAddAgentlessPermissions(packagePolicy, {
indices: dataStreamsForPermissions.map((ds) => getDataStreamPrivileges(ds, namespace)),
...clusterRoleDescriptor,
});
return [
packagePolicy.id,
maybeAddAdditionalPackagePoliciesPermissions(
packagePolicy,
maybeAddAgentlessPermissions(packagePolicy, {
indices: dataStreamsForPermissions.map((ds) => getDataStreamPrivileges(ds, namespace)),
...clusterRoleDescriptor,
})
),
];
});
return Object.fromEntries(permissionEntries);
@ -254,15 +261,41 @@ function universalProfilingPermissions(packagePolicyId: string): [string, Securi
function maybeAddAgentlessPermissions(
packagePolicy: PackagePolicy,
existing: SecurityRoleDescriptor
): [string, SecurityRoleDescriptor] {
): SecurityRoleDescriptor {
if (!packagePolicy.supports_agentless) {
return [packagePolicy.id, existing];
return existing;
}
existing.indices!.push({
names: ['agentless-*'],
privileges: AGENTLESS_INDEX_PERMISSIONS,
});
return [packagePolicy.id, existing];
return existing;
}
function maybeAddAdditionalPackagePoliciesPermissions(
packagePolicy: PackagePolicy,
existing?: SecurityRoleDescriptor
): SecurityRoleDescriptor | undefined {
if (
!packagePolicy.additional_datastreams_permissions ||
!packagePolicy.additional_datastreams_permissions.length
) {
return existing;
}
if (!existing) {
existing = {};
}
if (!existing.indices) {
existing.indices = [];
}
existing.indices!.push({
names: packagePolicy.additional_datastreams_permissions,
privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES,
});
return existing;
}
function apmPermissions(packagePolicyId: string): [string, SecurityRoleDescriptor] {

View file

@ -151,7 +151,10 @@ import {
import { getPackageAssetsMap } from './epm/packages/get';
import { validateAgentPolicyOutputForIntegration } from './agent_policies/outputs_helpers';
import type { PackagePolicyClientFetchAllItemIdsOptions } from './package_policy_service';
import { validatePolicyNamespaceForSpace } from './spaces/policy_namespaces';
import {
validateAdditionalDatastreamsPermissionsForSpace,
validatePolicyNamespaceForSpace,
} from './spaces/policy_namespaces';
import { isSpaceAwarenessEnabled, isSpaceAwarenessMigrationPending } from './spaces/helpers';
import { updatePackagePolicySpaces } from './spaces/package_policy';
import { runWithCache } from './epm/packages/cache';
@ -319,6 +322,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
spaceId: soClient.getCurrentNamespace(),
});
}
await validateAdditionalDatastreamsPermissionsForSpace({
additionalDatastreamsPermissions: enrichedPackagePolicy.additional_datastreams_permissions,
spaceId: soClient.getCurrentNamespace(),
});
let elasticsearchPrivileges: NonNullable<PackagePolicy['elasticsearch']>['privileges'];
let inputs = getInputsWithStreamIds(enrichedPackagePolicy, packagePolicyId);
@ -991,6 +998,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
spaceId: soClient.getCurrentNamespace(),
});
}
await validateAdditionalDatastreamsPermissionsForSpace({
additionalDatastreamsPermissions: enrichedPackagePolicy.additional_datastreams_permissions,
spaceId: soClient.getCurrentNamespace(),
});
// eslint-disable-next-line prefer-const
let { version, ...restOfPackagePolicy } = packagePolicy;
@ -1948,6 +1959,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
inputs: newPolicy.inputs[0]?.streams ? newPolicy.inputs : inputs,
vars: newPolicy.vars || newPP.vars,
supports_agentless: newPolicy.supports_agentless,
additional_datastreams_permissions: newPolicy.additional_datastreams_permissions,
};
}
}

View file

@ -42,3 +42,43 @@ export async function validatePolicyNamespaceForSpace({
);
}
}
export async function validateAdditionalDatastreamsPermissionsForSpace({
additionalDatastreamsPermissions,
spaceId,
}: {
additionalDatastreamsPermissions?: string[];
spaceId?: string;
}) {
const experimentalFeature = appContextService.getExperimentalFeatures();
if (!experimentalFeature.useSpaceAwareness) {
return;
}
const settings = await getSpaceSettings(spaceId);
if (
!settings.allowed_namespace_prefixes ||
settings.allowed_namespace_prefixes.length === 0 ||
!additionalDatastreamsPermissions ||
!additionalDatastreamsPermissions.length
) {
return;
}
for (const additionalDatastreamsPermission of additionalDatastreamsPermissions) {
let valid = false;
for (const allowedNamespacePrefix of settings.allowed_namespace_prefixes) {
if (additionalDatastreamsPermission.startsWith(allowedNamespacePrefix)) {
valid = true;
break;
}
}
if (!valid) {
throw new PolicyNamespaceValidationError(
`Invalid additionalDatastreamsPermission, supported namespace prefixes: ${settings.allowed_namespace_prefixes.join(
', '
)}`
);
}
}
}

View file

@ -182,6 +182,17 @@ export const PackagePolicyBaseSchema = {
})
)
),
additional_datastreams_permissions: schema.maybe(
schema.oneOf([
schema.literal(null),
schema.arrayOf(schema.string(), {
validate: validateAdditionalDatastreamsPermissions,
meta: {
description: 'Additional datastream permissions, that will be added to the agent policy.',
},
}),
])
),
};
export const NewPackagePolicySchema = schema.object({
@ -288,6 +299,17 @@ export const SimplifiedPackagePolicyInputsSchema = schema.maybe(
)
);
const VALIDATE_DATASTREAMS_PERMISSION_REGEX =
/^(logs)|(metrics)|(traces)|(synthetics)|(profiling)-(.*)$/;
function validateAdditionalDatastreamsPermissions(values: string[]) {
for (const val of values) {
if (!val.match(VALIDATE_DATASTREAMS_PERMISSION_REGEX)) {
return `${val} is not a valid datastream permissions, it should match logs|metrics|traces|synthetics|profiling)-*`;
}
}
}
export const SimplifiedPackagePolicyBaseSchema = schema.object({
id: schema.maybe(schema.string()),
name: schema.string(),
@ -306,6 +328,17 @@ export const SimplifiedPackagePolicyBaseSchema = schema.object({
})
)
),
additional_datastreams_permissions: schema.maybe(
schema.oneOf([
schema.literal(null),
schema.arrayOf(schema.string(), {
validate: validateAdditionalDatastreamsPermissions,
meta: {
description: 'Additional datastream permissions, that will be added to the agent policy.',
},
}),
])
),
});
export const SimplifiedPackagePolicyPreconfiguredSchema = SimplifiedPackagePolicyBaseSchema.extends(

View file

@ -12,6 +12,8 @@ import { v4 as uuidv4 } from 'uuid';
import { FtrProviderContext } from '../../../api_integration/ftr_provider_context';
import { skipIfNoDockerRegistry } from '../../helpers';
import { SpaceTestApiClient } from '../space_awareness/api_helper';
import { cleanFleetIndices, expectToRejectWithError } from '../space_awareness/helpers';
export default function (providerContext: FtrProviderContext) {
const { getService } = providerContext;
@ -19,6 +21,8 @@ export default function (providerContext: FtrProviderContext) {
const es: Client = getService('es');
const supertest = getService('supertest');
const kibanaServer = getService('kibanaServer');
const fleetAndAgents = getService('fleetAndAgents');
const apiClient = new SpaceTestApiClient(supertest);
const getPackagePolicyById = async (id: string) => {
const { body } = await supertest.get(`/api/fleet/package_policies/${id}`);
@ -30,34 +34,24 @@ export default function (providerContext: FtrProviderContext) {
let agentPolicyId2: string;
before(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await getService('esArchiver').load(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
const { body: agentPolicyResponse } = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
name: `Test policy ${uuidv4()}`,
namespace: 'default',
})
.expect(200);
agentPolicyId = agentPolicyResponse.item.id;
await cleanFleetIndices(es);
await fleetAndAgents.setup();
const { body: agentPolicyResponse2 } = await supertest
.post(`/api/fleet/agent_policies`)
.set('kbn-xsrf', 'xxxx')
.send({
const [agentPolicyResponse, agentPolicyResponse2] = await Promise.all([
apiClient.createAgentPolicy(undefined, {
name: `Test policy ${uuidv4()}`,
namespace: 'default',
})
.expect(200);
}),
apiClient.createAgentPolicy(undefined, {
name: `Test policy ${uuidv4()}`,
namespace: 'default',
}),
]);
agentPolicyId = agentPolicyResponse.item.id;
agentPolicyId2 = agentPolicyResponse2.item.id;
});
after(async () => {
await kibanaServer.savedObjects.cleanStandardList();
await getService('esArchiver').unload(
'x-pack/test/functional/es_archives/fleet/empty_fleet_server'
);
await supertest
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
@ -66,6 +60,9 @@ export default function (providerContext: FtrProviderContext) {
.post(`/api/fleet/agent_policies/delete`)
.set('kbn-xsrf', 'xxxx')
.send({ agentPolicyId: agentPolicyId2 });
await kibanaServer.savedObjects.cleanStandardList();
await cleanFleetIndices(es);
});
it('can only add to hosted agent policies using the force parameter', async function () {
@ -261,6 +258,11 @@ export default function (providerContext: FtrProviderContext) {
});
it('should not allow multiple limited packages on the same agent policy', async function () {
await apiClient.installPackage({
pkgName: 'endpoint',
pkgVersion: '8.5.0',
force: true,
});
await supertest
.post(`/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
@ -274,7 +276,7 @@ export default function (providerContext: FtrProviderContext) {
package: {
name: 'endpoint',
title: 'Endpoint',
version: '8.4.0',
version: '8.5.0',
},
force: true,
})
@ -524,6 +526,85 @@ export default function (providerContext: FtrProviderContext) {
.expect(200);
});
it('should support additional_datastreams_permissions', async () => {
const createPackagePolicyRes = await apiClient.createPackagePolicy(undefined, {
name: 'filetest-3-' + Date.now(),
description: '',
namespace: 'default',
policy_ids: [agentPolicyId],
enabled: true,
inputs: [
{
enabled: true,
streams: [],
type: 'single_input',
},
],
package: {
name: 'filetest',
title: 'For File Tests',
version: '0.1.0',
},
additional_datastreams_permissions: ['logs-tata-default', 'metrics-tata-default'],
} as any);
const getPackagePolicyRes = await apiClient.getPackagePolicy(createPackagePolicyRes.item.id);
expect(getPackagePolicyRes.item.additional_datastreams_permissions).to.eql([
'logs-tata-default',
'metrics-tata-default',
]);
const policyDocRes = await es.search({
index: '.fleet-policies',
sort: [{ '@timestamp': 'desc' }],
query: {
term: {
policy_id: agentPolicyId,
},
},
});
const packagePolicyPermission = (policyDocRes.hits?.hits[0]._source as any).data
?.output_permissions?.default?.[createPackagePolicyRes.item.id];
expect(
packagePolicyPermission.indices.find((permissions: any) =>
permissions.names.includes('logs-tata-default')
)
).to.eql({
names: ['logs-tata-default', 'metrics-tata-default'],
privileges: ['auto_configure', 'create_doc'],
});
});
it('should throw with invalid additional_datastreams_permissions', async () => {
await expectToRejectWithError(
() =>
apiClient.createPackagePolicy(undefined, {
name: 'filetest-3-' + Date.now(),
description: '',
namespace: 'default',
policy_ids: [agentPolicyId],
enabled: true,
inputs: [
{
enabled: true,
streams: [],
type: 'single_input',
},
],
package: {
name: 'filetest',
title: 'For File Tests',
version: '0.1.0',
},
additional_datastreams_permissions: ['invalid-tata-default', 'metrics-tata-default'],
} as any),
/400 "Bad Request"/
);
});
it('should return 200 and formatted inputs when the format=simplified query param is passed', async function () {
const { body } = await supertest
.post(`/api/fleet/package_policies?format=simplified`)
@ -989,6 +1070,60 @@ export default function (providerContext: FtrProviderContext) {
})
.expect(400);
});
it('should support additional_datastreams_permissions', async () => {
const createPackagePolicyRes = await apiClient.createPackagePolicy(undefined, {
name: `create-simplified-package-policy-required-variables-${Date.now()}`,
description: '',
namespace: 'default',
policy_ids: [agentPolicyId],
inputs: {
'with_required_variables-test_input': {
streams: {
'with_required_variables.log': {
vars: { test_var_required: 'I am required' },
},
},
},
},
package: {
name: 'with_required_variables',
version: '0.1.0',
},
additional_datastreams_permissions: ['logs-test-default', 'metrics-test-default'],
});
const getPackagePolicyRes = await apiClient.getPackagePolicy(
createPackagePolicyRes.item.id
);
expect(getPackagePolicyRes.item.additional_datastreams_permissions).to.eql([
'logs-test-default',
'metrics-test-default',
]);
const policyDocRes = await es.search({
index: '.fleet-policies',
sort: [{ '@timestamp': 'desc' }],
query: {
term: {
policy_id: agentPolicyId,
},
},
});
const packagePolicyPermission = (policyDocRes.hits?.hits[0]._source as any).data
?.output_permissions?.default?.[createPackagePolicyRes.item.id];
expect(
packagePolicyPermission.indices.find((permissions: any) =>
permissions.names.includes('logs-test-default')
)
).to.eql({
names: ['logs-test-default', 'metrics-test-default'],
privileges: ['auto_configure', 'create_doc'],
});
});
});
describe('Package verification', () => {

View file

@ -97,13 +97,20 @@ export class SpaceTestApiClient {
spaceId?: string,
data: Partial<SimplifiedPackagePolicy & { package: { name: string; version: string } }> = {}
): Promise<CreatePackagePolicyResponse> {
const { body: res } = await this.supertest
const { body: res, statusCode } = await this.supertest
.post(`${this.getBaseUrl(spaceId)}/api/fleet/package_policies`)
.set('kbn-xsrf', 'xxxx')
.send(data)
.expect(200);
.send(data);
return res;
if (statusCode === 200) {
return res;
}
if (statusCode === 404) {
throw new Error('404 "Not Found"');
} else {
throw new Error(`${statusCode} "${res?.error}" ${res.message}`);
}
}
async getPackagePolicy(
packagePolicyId: string,