From 4176e8f066324acb75ccf8f9e37a9970a7ab0a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Thu, 1 Apr 2021 14:43:55 +0200 Subject: [PATCH 1/9] Ensure packages' data_streams contain all metadata We can read the package information from two places - The EPM registry (method `getRegistryPackage()`) - The local ES cache (method `getEsPackage()`) The package contains a property called `data_streams`. The contents of this property varied depending on from where the package was being loaded. The code in `getEsPackage` was cherry-picking what properties to load to do validation. We have changed the code so we cherry-pick only the properties that need some sort of validation, and pass the others in bulk. --- .../plugins/fleet/server/services/epm/archive/storage.ts | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts index 2db6009270a3b6..ffe279ed82d548 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts @@ -224,24 +224,19 @@ export const getEsPackage = async ( ); const dataStreamManifest = safeLoad(soResDataStreamManifest.attributes.data_utf8); const { - title: dataStreamTitle, - release, ingest_pipeline: ingestPipeline, - type, dataset, streams: manifestStreams, + ...dataStreamManifestProps } = dataStreamManifest; const streams = parseAndVerifyStreams(manifestStreams, dataStreamPath); dataStreams.push({ dataset: dataset || `${pkgName}.${dataStreamPath}`, - title: dataStreamTitle, - release, - package: pkgName, ingest_pipeline: ingestPipeline || 'default', path: dataStreamPath, - type, streams, + ...dataStreamManifestProps, }); }) ); From 8b905292f9449764be2393b223702f5e16fcb04b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Thu, 1 Apr 2021 17:30:52 +0200 Subject: [PATCH 2/9] Add `getPackagePermissions` method Returns a list of the necessary ES permissions based on the permissions specified in each data_stream. If no permissions are specified it returns a default set of permissions for each data_stream. --- .../plugins/fleet/common/types/models/epm.ts | 12 ++++++ .../fleet/server/services/epm/packages/get.ts | 40 +++++++++++++++++++ 2 files changed, 52 insertions(+) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index 80fabd51613aeb..88c75e6eb447f1 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -254,6 +254,7 @@ export enum RegistryDataStreamKeys { ingest_pipeline = 'ingest_pipeline', elasticsearch = 'elasticsearch', dataset_is_prefix = 'dataset_is_prefix', + permissions = 'permissions', } export interface RegistryDataStream { @@ -269,6 +270,7 @@ export interface RegistryDataStream { [RegistryDataStreamKeys.ingest_pipeline]: string; [RegistryDataStreamKeys.elasticsearch]?: RegistryElasticsearch; [RegistryDataStreamKeys.dataset_is_prefix]?: boolean; + [RegistryDataStreamKeys.permissions]?: RegistryDataStreamPermissions; } export interface RegistryElasticsearch { @@ -276,6 +278,16 @@ export interface RegistryElasticsearch { 'index_template.mappings'?: object; } +export interface RegistryDataStreamPermissions { + cluster?: string[]; + indices?: string[]; +} + +export interface PackagePermissions { + cluster: string[]; + indices: Array<{ names: string[]; privileges: string[] }>; +} + export type RegistryVarType = 'integer' | 'bool' | 'password' | 'text' | 'yaml' | 'string'; export enum RegistryVarsEntryKeys { name = 'name', diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index 98dbd3bd571621..c012bf9ff495df 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -18,6 +18,7 @@ import type { ArchivePackage, RegistryPackage, EpmPackageAdditions, + PackagePermissions, } from '../../../../common/types'; import type { KibanaAssetType } from '../../../types'; import type { Installation, PackageInfo } from '../../../types'; @@ -169,6 +170,45 @@ export const getPackageUsageStats = async ({ }; }; +export async function getPackagePermissions( + soClient: SavedObjectsClientContract, + pkgName: string, + pkgVersion: string, + namespace = '*' +): Promise { + const pkg = await getPackageInfo({ savedObjectsClient: soClient, pkgName, pkgVersion }); + if (!pkg.data_streams) { + return undefined; + } + + const clusterPermissions = new Set(); + const indices: PackagePermissions['indices'] = pkg.data_streams!.map((ds) => { + if (ds.permissions?.cluster) { + ds.permissions.cluster.forEach((p) => clusterPermissions.add(p)); + } else { + clusterPermissions.add('monitor'); + } + + let index = `${ds.type}-${ds.dataset}-${namespace}`; + if (ds.dataset_is_prefix) { + index = `${index}-*`; + } + if (ds.hidden) { + index = `.${index}`; + } + + return { + names: [index], + privileges: ds.permissions?.indices ?? ['auto_configure', 'create_doc'], + }; + }); + + return { + cluster: Array.from(clusterPermissions), + indices, + }; +} + interface PackageResponse { paths: string[]; packageInfo: ArchivePackage | RegistryPackage; From a57160f640e76a53bc39aeccd2ddc77fa51c62ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Thu, 1 Apr 2021 17:32:27 +0200 Subject: [PATCH 3/9] Expose tight permissions in the agent policy --- .../fleet/common/types/models/agent_policy.ts | 9 +--- .../fleet/server/services/agent_policy.ts | 53 ++++++++++++++----- 2 files changed, 43 insertions(+), 19 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/agent_policy.ts b/x-pack/plugins/fleet/common/types/models/agent_policy.ts index 439d00695a7376..d6516014ef3665 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -10,6 +10,7 @@ import type { DataType, ValueOf } from '../../types'; import type { PackagePolicy, PackagePolicyPackage } from './package_policy'; import type { Output } from './output'; +import type { PackagePermissions } from './epm'; export type AgentPolicyStatus = typeof agentPolicyStatuses; @@ -61,13 +62,7 @@ export interface FullAgentPolicyInput { } export interface FullAgentPolicyOutputPermissions { - [role: string]: { - cluster: string[]; - indices: Array<{ - names: string[]; - privileges: string[]; - }>; - }; + [role: string]: PackagePermissions; } export interface FullAgentPolicy { diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index fa71a67025e3ee..7daf10382d126d 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -38,6 +38,7 @@ import { AGENT_POLICY_INDEX, DEFAULT_FLEET_SERVER_AGENT_POLICY, } from '../../common'; +import type { PackagePermissions } from '../../common'; import type { DeleteAgentPolicyResponse, Settings, @@ -61,9 +62,20 @@ import { getSettings } from './settings'; import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object'; import { isAgentsSetup } from './agents/setup'; import { appContextService } from './app_context'; +import { getPackagePermissions } from './epm/packages/get'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; +const DEFAULT_PERMISSIONS: PackagePermissions = { + cluster: ['monitor'], + indices: [ + { + names: ['logs-*', 'metrics-*', 'traces-*', '.logs-endpoint.diagnostic.collection-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], +}; + class AgentPolicyService { private triggerAgentPolicyUpdatedEvent = async ( soClient: SavedObjectsClientContract, @@ -737,24 +749,41 @@ class AgentPolicyService { }), }; + const permissions = Object.fromEntries( + await Promise.all( + // Original type is `string[] | PackagePolicy[]`, but TS doesn't allow to `map()` over that. + (agentPolicy.package_policies as Array).map( + async (packagePolicy): Promise<[string, PackagePermissions]> => { + if (typeof packagePolicy === 'string' || !packagePolicy.package) { + return ['_fallback', DEFAULT_PERMISSIONS]; + } + + const { name, version } = packagePolicy.package; + + const packagePermissions = await getPackagePermissions( + soClient, + name, + version, + packagePolicy.namespace + ); + + return packagePermissions + ? [packagePolicy.name, packagePermissions] + : ['_fallback', DEFAULT_PERMISSIONS]; + } + ) + ) + ); + // Only add permissions if output.type is "elasticsearch" fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< NonNullable - >((permissions, outputName) => { + >((p, outputName) => { const output = fullAgentPolicy.outputs[outputName]; if (output && output.type === 'elasticsearch') { - permissions[outputName] = {}; - permissions[outputName]._fallback = { - cluster: ['monitor'], - indices: [ - { - names: ['logs-*', 'metrics-*', 'traces-*', '.logs-endpoint.diagnostic.collection-*'], - privileges: ['auto_configure', 'create_doc'], - }, - ], - }; + p[outputName] = permissions; } - return permissions; + return p; }, {}); // only add settings if not in standalone From 754d6f94a1a010e2c97c1d4aff75df1ae7d3e4de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Mon, 12 Apr 2021 13:42:44 +0200 Subject: [PATCH 4/9] Restore missing `package` prop --- x-pack/plugins/fleet/server/services/epm/archive/storage.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts index ffe279ed82d548..32d499b07ba116 100644 --- a/x-pack/plugins/fleet/server/services/epm/archive/storage.ts +++ b/x-pack/plugins/fleet/server/services/epm/archive/storage.ts @@ -232,6 +232,7 @@ export const getEsPackage = async ( const streams = parseAndVerifyStreams(manifestStreams, dataStreamPath); dataStreams.push({ + package: pkgName, dataset: dataset || `${pkgName}.${dataStreamPath}`, ingest_pipeline: ingestPipeline || 'default', path: dataStreamPath, From 579278c024b49a7128bcc637362c736c1844529f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 13 Apr 2021 11:10:51 +0200 Subject: [PATCH 5/9] Add config flag to enable/disable the permissions --- x-pack/plugins/fleet/common/types/index.ts | 1 + .../fleet/public/applications/fleet/mock/plugin_configuration.ts | 1 + x-pack/plugins/fleet/server/index.ts | 1 + 3 files changed, 3 insertions(+) diff --git a/x-pack/plugins/fleet/common/types/index.ts b/x-pack/plugins/fleet/common/types/index.ts index 1984de79a6357e..b48f99c3481848 100644 --- a/x-pack/plugins/fleet/common/types/index.ts +++ b/x-pack/plugins/fleet/common/types/index.ts @@ -31,6 +31,7 @@ export interface FleetConfigType { }; agentPolicyRolloutRateLimitIntervalMs: number; agentPolicyRolloutRateLimitRequestPerInterval: number; + agentPolicyTightPermissions: boolean; }; } diff --git a/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts b/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts index 81ef6a6703c343..0026d254bd94b2 100644 --- a/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts +++ b/x-pack/plugins/fleet/public/applications/fleet/mock/plugin_configuration.ts @@ -28,6 +28,7 @@ export const createConfigurationMock = (): FleetConfigType => { }, agentPolicyRolloutRateLimitIntervalMs: 100, agentPolicyRolloutRateLimitRequestPerInterval: 1000, + agentPolicyTightPermissions: false, }, }; }; diff --git a/x-pack/plugins/fleet/server/index.ts b/x-pack/plugins/fleet/server/index.ts index 0178b801f4d2fc..2e67d6ae5de08b 100644 --- a/x-pack/plugins/fleet/server/index.ts +++ b/x-pack/plugins/fleet/server/index.ts @@ -76,6 +76,7 @@ export const config: PluginConfigDescriptor = { agentPolicyRolloutRateLimitRequestPerInterval: schema.number({ defaultValue: AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL, }), + agentPolicyTightPermissions: schema.boolean({ defaultValue: false }), }), }), }; From e7053c4550237e239b9abb86a0a8de052f25d4db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 13 Apr 2021 12:01:42 +0200 Subject: [PATCH 6/9] Use config flag to send tight permissions with the agent --- .../fleet/server/services/agent_policy.ts | 57 +++++++++++-------- 1 file changed, 33 insertions(+), 24 deletions(-) diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 50abe11cdbb4d6..054fa3d6baff0e 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -38,7 +38,7 @@ import { AGENT_POLICY_INDEX, DEFAULT_FLEET_SERVER_AGENT_POLICY, } from '../../common'; -import type { PackagePermissions } from '../../common'; +import type { FullAgentPolicyOutputPermissions, PackagePermissions } from '../../common'; import type { DeleteAgentPolicyResponse, Settings, @@ -749,31 +749,40 @@ class AgentPolicyService { }), }; - const permissions = Object.fromEntries( - await Promise.all( - // Original type is `string[] | PackagePolicy[]`, but TS doesn't allow to `map()` over that. - (agentPolicy.package_policies as Array).map( - async (packagePolicy): Promise<[string, PackagePermissions]> => { - if (typeof packagePolicy === 'string' || !packagePolicy.package) { - return ['_fallback', DEFAULT_PERMISSIONS]; + const hasTightPermissions = appContextService.getConfig()?.agents.agentPolicyTightPermissions; + let permissions: FullAgentPolicyOutputPermissions; + + if (hasTightPermissions) { + permissions = Object.fromEntries( + await Promise.all( + // Original type is `string[] | PackagePolicy[]`, but TS doesn't allow to `map()` over that. + (agentPolicy.package_policies as Array).map( + async (packagePolicy): Promise<[string, PackagePermissions]> => { + if (typeof packagePolicy === 'string' || !packagePolicy.package) { + return ['_fallback', DEFAULT_PERMISSIONS]; + } + + const { name, version } = packagePolicy.package; + + const packagePermissions = await getPackagePermissions( + soClient, + name, + version, + packagePolicy.namespace + ); + + return packagePermissions + ? [packagePolicy.name, packagePermissions] + : ['_fallback', DEFAULT_PERMISSIONS]; } - - const { name, version } = packagePolicy.package; - - const packagePermissions = await getPackagePermissions( - soClient, - name, - version, - packagePolicy.namespace - ); - - return packagePermissions - ? [packagePolicy.name, packagePermissions] - : ['_fallback', DEFAULT_PERMISSIONS]; - } + ) ) - ) - ); + ); + } else { + permissions = { + _fallback: DEFAULT_PERMISSIONS, + }; + } // Only add permissions if output.type is "elasticsearch" fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< From 66b693f886cb8dadd239007bf77f2a02e9882296 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 13 Apr 2021 16:48:25 +0200 Subject: [PATCH 7/9] Extract `getPackagePermissions` to its own file --- .../fleet/server/services/agent_policy.ts | 3 +- .../fleet/server/services/epm/packages/get.ts | 40 --------------- .../server/services/epm/packages/index.ts | 2 + .../services/epm/packages/permissions.ts | 51 +++++++++++++++++++ 4 files changed, 54 insertions(+), 42 deletions(-) create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/permissions.ts diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index feb42ec85df900..ea9d138fe0d183 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -53,7 +53,7 @@ import { } from '../errors'; import { getFullAgentPolicyKibanaConfig } from '../../common/services/full_agent_policy_kibana_config'; -import { getPackageInfo } from './epm/packages'; +import { getPackageInfo, getPackagePermissions } from './epm/packages'; import { createAgentPolicyAction, getAgentsByKuery } from './agents'; import { packagePolicyService } from './package_policy'; import { outputService } from './output'; @@ -62,7 +62,6 @@ import { getSettings } from './settings'; import { normalizeKuery, escapeSearchQueryPhrase } from './saved_object'; import { isAgentsSetup } from './agents/setup'; import { appContextService } from './app_context'; -import { getPackagePermissions } from './epm/packages/get'; const SAVED_OBJECT_TYPE = AGENT_POLICY_SAVED_OBJECT_TYPE; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/get.ts b/x-pack/plugins/fleet/server/services/epm/packages/get.ts index c012bf9ff495df..98dbd3bd571621 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/get.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/get.ts @@ -18,7 +18,6 @@ import type { ArchivePackage, RegistryPackage, EpmPackageAdditions, - PackagePermissions, } from '../../../../common/types'; import type { KibanaAssetType } from '../../../types'; import type { Installation, PackageInfo } from '../../../types'; @@ -170,45 +169,6 @@ export const getPackageUsageStats = async ({ }; }; -export async function getPackagePermissions( - soClient: SavedObjectsClientContract, - pkgName: string, - pkgVersion: string, - namespace = '*' -): Promise { - const pkg = await getPackageInfo({ savedObjectsClient: soClient, pkgName, pkgVersion }); - if (!pkg.data_streams) { - return undefined; - } - - const clusterPermissions = new Set(); - const indices: PackagePermissions['indices'] = pkg.data_streams!.map((ds) => { - if (ds.permissions?.cluster) { - ds.permissions.cluster.forEach((p) => clusterPermissions.add(p)); - } else { - clusterPermissions.add('monitor'); - } - - let index = `${ds.type}-${ds.dataset}-${namespace}`; - if (ds.dataset_is_prefix) { - index = `${index}-*`; - } - if (ds.hidden) { - index = `.${index}`; - } - - return { - names: [index], - privileges: ds.permissions?.indices ?? ['auto_configure', 'create_doc'], - }; - }); - - return { - cluster: Array.from(clusterPermissions), - indices, - }; -} - interface PackageResponse { paths: string[]; packageInfo: ArchivePackage | RegistryPackage; diff --git a/x-pack/plugins/fleet/server/services/epm/packages/index.ts b/x-pack/plugins/fleet/server/services/epm/packages/index.ts index c85376ef177b3f..7b54c46a74fd3a 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/index.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/index.ts @@ -24,6 +24,8 @@ export { SearchParams, } from './get'; +export { getPackagePermissions } from './permissions'; + export { BulkInstallResponse, IBulkInstallPackageError, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts b/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts new file mode 100644 index 00000000000000..4dd77f03c8f210 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts @@ -0,0 +1,51 @@ +/* + * 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 { SavedObjectsClientContract } from 'src/core/server'; + +import type { PackagePermissions } from '../../../../common/types'; + +import { getPackageInfo } from './get'; + +export async function getPackagePermissions( + soClient: SavedObjectsClientContract, + pkgName: string, + pkgVersion: string, + namespace = '*' +): Promise { + const pkg = await getPackageInfo({ savedObjectsClient: soClient, pkgName, pkgVersion }); + if (!pkg.data_streams) { + return undefined; + } + + const clusterPermissions = new Set(); + const indices: PackagePermissions['indices'] = pkg.data_streams!.map((ds) => { + if (ds.permissions?.cluster) { + ds.permissions.cluster.forEach((p) => clusterPermissions.add(p)); + } else { + clusterPermissions.add('monitor'); + } + + let index = `${ds.type}-${ds.dataset}-${namespace}`; + if (ds.dataset_is_prefix) { + index = `${index}-*`; + } + if (ds.hidden) { + index = `.${index}`; + } + + return { + names: [index], + privileges: ds.permissions?.indices ?? ['auto_configure', 'create_doc'], + }; + }); + + return { + cluster: Array.from(clusterPermissions), + indices, + }; +} From b6b1337f394ce6aabdc252977c63ce29e4651bdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Tue, 13 Apr 2021 16:48:54 +0200 Subject: [PATCH 8/9] Add tests to `getPackagePermissions` --- .../services/epm/packages/permissions.test.ts | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 x-pack/plugins/fleet/server/services/epm/packages/permissions.test.ts diff --git a/x-pack/plugins/fleet/server/services/epm/packages/permissions.test.ts b/x-pack/plugins/fleet/server/services/epm/packages/permissions.test.ts new file mode 100644 index 00000000000000..21f7473afd8fff --- /dev/null +++ b/x-pack/plugins/fleet/server/services/epm/packages/permissions.test.ts @@ -0,0 +1,255 @@ +/* + * 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. + */ + +jest.mock('./get', () => ({ getPackageInfo: jest.fn(async () => ({})) })); + +import type { SavedObjectsClientContract } from 'kibana/server'; + +import { savedObjectsClientMock } from '../../../../../../../src/core/server/mocks'; +import type { PackageInfo, RegistryDataStream } from '../../../types'; + +import { getPackagePermissions } from './permissions'; +import { getPackageInfo } from './get'; + +const getPackageInfoMock = getPackageInfo as jest.MockedFunction; + +const PACKAGE = 'test_package'; +const VERSION = '1.0.0'; + +function createFakePackage(props: Partial = {}): PackageInfo { + const name = PACKAGE; + const version = VERSION; + + return { + name, + version, + latestVersion: version, + release: 'experimental', + format_version: '1.0.0', + title: name, + description: '', + icons: [], + owner: { github: '' }, + status: 'not_installed', + assets: { + kibana: { + dashboard: [], + visualization: [], + search: [], + index_pattern: [], + map: [], + lens: [], + security_rule: [], + ml_module: [], + }, + elasticsearch: { + component_template: [], + ingest_pipeline: [], + ilm_policy: [], + transform: [], + index_template: [], + data_stream_ilm_policy: [], + }, + }, + ...props, + } as PackageInfo; +} + +function createFakeDataset( + type: 'logs' | 'metrics' | 'traces' | 'synthetics', + dataset: string, + props: Partial = {} +): RegistryDataStream { + return { + type, + dataset, + title: dataset, + package: PACKAGE, + release: VERSION, + path: `/${dataset}/`, + ingest_pipeline: '', + ...props, + }; +} + +describe('epm/permissions', () => { + let soClient: jest.Mocked; + + beforeEach(() => { + soClient = savedObjectsClientMock.create(); + getPackageInfoMock.mockReset(); + }); + + describe('getPackagePermissions()', () => { + it('Returns `undefined` if package does not have datasets', async () => { + getPackageInfoMock.mockResolvedValueOnce(createFakePackage()); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toBeUndefined(); + }); + + it('Returns empty permissions if datasets are empty', async () => { + getPackageInfoMock.mockResolvedValueOnce(createFakePackage({ data_streams: [] })); + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toMatchObject({ cluster: [], indices: [] }); + }); + + it('Returns default permissions', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [createFakeDataset('logs', 'dataset')], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toMatchObject({ + cluster: ['monitor'], + indices: [ + { + names: ['logs-dataset-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }); + }); + + it('Returns default permissions for multiple datasets', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [ + createFakeDataset('logs', 'dataset1'), + createFakeDataset('metrics', 'dataset2'), + ], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toMatchObject({ + cluster: ['monitor'], + indices: [ + { + names: ['logs-dataset1-*'], + privileges: ['auto_configure', 'create_doc'], + }, + { + names: ['metrics-dataset2-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }); + }); + + it('Passes the namespace to the datasets', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [createFakeDataset('logs', 'dataset')], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION, 'test'); + expect(permissions).toMatchObject({ + cluster: ['monitor'], + indices: [ + { + names: ['logs-dataset-test'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }); + }); + + it('Handles hidden datasets', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [createFakeDataset('logs', 'dataset', { hidden: true })], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toMatchObject({ + cluster: ['monitor'], + indices: [ + { + names: ['.logs-dataset-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }); + }); + + it('Handles prefix datasets', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [createFakeDataset('logs', 'dataset', { dataset_is_prefix: true })], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION, 'test'); + expect(permissions).toMatchObject({ + cluster: ['monitor'], + indices: [ + { + names: ['logs-dataset-test-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }); + }); + + it('Aggregates cluster permissions from the different datasets', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [ + createFakeDataset('logs', 'dataset', { permissions: { cluster: ['foo', 'bar'] } }), + createFakeDataset('metrics', 'dataset', { permissions: { cluster: ['foo', 'baz'] } }), + ], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toMatchObject({ + cluster: ['foo', 'bar', 'baz'], + indices: [ + { + names: ['logs-dataset-*'], + privileges: ['auto_configure', 'create_doc'], + }, + { + names: ['metrics-dataset-*'], + privileges: ['auto_configure', 'create_doc'], + }, + ], + }); + }); + + it('Configures indices permissions for each datasetdatasets', async () => { + getPackageInfoMock.mockResolvedValueOnce( + createFakePackage({ + data_streams: [ + createFakeDataset('logs', 'dataset', { permissions: { indices: ['foo', 'bar'] } }), + createFakeDataset('metrics', 'dataset', { permissions: { indices: ['foo', 'baz'] } }), + ], + }) + ); + + const permissions = await getPackagePermissions(soClient, PACKAGE, VERSION); + expect(permissions).toMatchObject({ + cluster: ['monitor'], + indices: [ + { + names: ['logs-dataset-*'], + privileges: ['foo', 'bar'], + }, + { + names: ['metrics-dataset-*'], + privileges: ['foo', 'baz'], + }, + ], + }); + }); + }); +}); From fb9052505351f9a7e393855042e3dfcfcfe68f77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Fern=C3=A1ndez=20G=C3=B3mez?= Date: Wed, 14 Apr 2021 16:25:02 +0200 Subject: [PATCH 9/9] Move default cluster permissions out of `getPackagePermissions` --- x-pack/plugins/fleet/common/types/models/epm.ts | 4 ++-- x-pack/plugins/fleet/server/services/agent_policy.ts | 3 +++ .../plugins/fleet/server/services/epm/packages/permissions.ts | 4 +--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/epm.ts b/x-pack/plugins/fleet/common/types/models/epm.ts index f3ede0617837b0..65fbb300b14798 100644 --- a/x-pack/plugins/fleet/common/types/models/epm.ts +++ b/x-pack/plugins/fleet/common/types/models/epm.ts @@ -286,8 +286,8 @@ export interface RegistryDataStreamPermissions { } export interface PackagePermissions { - cluster: string[]; - indices: Array<{ names: string[]; privileges: string[] }>; + cluster?: string[]; + indices?: Array<{ names: string[]; privileges: string[] }>; } export type RegistryVarType = 'integer' | 'bool' | 'password' | 'text' | 'yaml' | 'string'; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index ea9d138fe0d183..40c37c850e3c95 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -779,6 +779,9 @@ class AgentPolicyService { ) ) ); + permissions._elastic_agent_checks = { + cluster: DEFAULT_PERMISSIONS.cluster, + }; } else { permissions = { _fallback: DEFAULT_PERMISSIONS, diff --git a/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts b/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts index 4dd77f03c8f210..4b934639a0f50e 100644 --- a/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts +++ b/x-pack/plugins/fleet/server/services/epm/packages/permissions.ts @@ -26,8 +26,6 @@ export async function getPackagePermissions( const indices: PackagePermissions['indices'] = pkg.data_streams!.map((ds) => { if (ds.permissions?.cluster) { ds.permissions.cluster.forEach((p) => clusterPermissions.add(p)); - } else { - clusterPermissions.add('monitor'); } let index = `${ds.type}-${ds.dataset}-${namespace}`; @@ -45,7 +43,7 @@ export async function getPackagePermissions( }); return { - cluster: Array.from(clusterPermissions), + cluster: clusterPermissions.size > 0 ? Array.from(clusterPermissions) : undefined, indices, }; }