diff --git a/x-pack/plugins/fleet/common/constants/agent_policy.ts b/x-pack/plugins/fleet/common/constants/agent_policy.ts index d0bde39e9f2d7..95078d8ead84f 100644 --- a/x-pack/plugins/fleet/common/constants/agent_policy.ts +++ b/x-pack/plugins/fleet/common/constants/agent_policy.ts @@ -11,3 +11,17 @@ export const agentPolicyStatuses = { Active: 'active', Inactive: 'inactive', } as const; + +export const AGENT_POLICY_DEFAULT_MONITORING_DATASETS = [ + 'elastic_agent', + 'elastic_agent.elastic_agent', + 'elastic_agent.apm_server', + 'elastic_agent.filebeat', + 'elastic_agent.fleet_server', + 'elastic_agent.metricbeat', + 'elastic_agent.osquerybeat', + 'elastic_agent.packetbeat', + 'elastic_agent.endpoint_security', + 'elastic_agent.auditbeat', + 'elastic_agent.heartbeat', +]; diff --git a/x-pack/plugins/fleet/server/constants/index.ts b/x-pack/plugins/fleet/server/constants/index.ts index 0dcd5e7f47800..2ce457242c6b5 100644 --- a/x-pack/plugins/fleet/server/constants/index.ts +++ b/x-pack/plugins/fleet/server/constants/index.ts @@ -50,6 +50,7 @@ export { DEFAULT_OUTPUT, DEFAULT_PACKAGES, PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + AGENT_POLICY_DEFAULT_MONITORING_DATASETS, // Fleet Server index FLEET_SERVER_SERVERS_INDEX, ENROLLMENT_API_KEYS_INDEX, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap index 970bccbafa634..36044d35703ee 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/full_agent_policy.test.ts.snap @@ -51,25 +51,14 @@ Object { "cluster": Array [ "monitor", ], + }, + "_elastic_agent_monitoring": Object { "indices": Array [ Object { "names": Array [ - "metrics-elastic_agent-default", - "metrics-elastic_agent.elastic_agent-default", - "metrics-elastic_agent.apm_server-default", - "metrics-elastic_agent.filebeat-default", - "metrics-elastic_agent.fleet_server-default", - "metrics-elastic_agent.metricbeat-default", - "metrics-elastic_agent.osquerybeat-default", - "metrics-elastic_agent.packetbeat-default", - "metrics-elastic_agent.endpoint_security-default", - "metrics-elastic_agent.auditbeat-default", - "metrics-elastic_agent.heartbeat-default", - ], - "privileges": Array [ - "auto_configure", - "create_doc", + "metrics-default", ], + "privileges": Array [], }, ], }, @@ -148,25 +137,14 @@ Object { "cluster": Array [ "monitor", ], + }, + "_elastic_agent_monitoring": Object { "indices": Array [ Object { "names": Array [ - "metrics-elastic_agent-default", - "metrics-elastic_agent.elastic_agent-default", - "metrics-elastic_agent.apm_server-default", - "metrics-elastic_agent.filebeat-default", - "metrics-elastic_agent.fleet_server-default", - "metrics-elastic_agent.metricbeat-default", - "metrics-elastic_agent.osquerybeat-default", - "metrics-elastic_agent.packetbeat-default", - "metrics-elastic_agent.endpoint_security-default", - "metrics-elastic_agent.auditbeat-default", - "metrics-elastic_agent.heartbeat-default", - ], - "privileges": Array [ - "auto_configure", - "create_doc", + "metrics-default", ], + "privileges": Array [], }, ], }, @@ -245,25 +223,14 @@ Object { "cluster": Array [ "monitor", ], + }, + "_elastic_agent_monitoring": Object { "indices": Array [ Object { "names": Array [ - "metrics-elastic_agent-default", - "metrics-elastic_agent.elastic_agent-default", - "metrics-elastic_agent.apm_server-default", - "metrics-elastic_agent.filebeat-default", - "metrics-elastic_agent.fleet_server-default", - "metrics-elastic_agent.metricbeat-default", - "metrics-elastic_agent.osquerybeat-default", - "metrics-elastic_agent.packetbeat-default", - "metrics-elastic_agent.endpoint_security-default", - "metrics-elastic_agent.auditbeat-default", - "metrics-elastic_agent.heartbeat-default", - ], - "privileges": Array [ - "auto_configure", - "create_doc", + "metrics-default", ], + "privileges": Array [], }, ], }, diff --git a/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap new file mode 100644 index 0000000000000..a54d4beb6c041 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/__snapshots__/monitoring_permissions.test.ts.snap @@ -0,0 +1,195 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getMonitoringPermissions With elastic agent package installed should return default logs and metrics permissions if both are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "metrics-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "logs-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "metrics-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions With elastic agent package installed should return default logs permissions if only logs are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "logs-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions With elastic agent package installed should return default metrics permissions if only metrics are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent.metricbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + Object { + "names": Array [ + "metrics-elastic_agent.filebeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions Without elastic agent package installed should return default logs and metrics permissions if both are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent-testnamespace123", + "logs-elastic_agent.elastic_agent-testnamespace123", + "logs-elastic_agent.apm_server-testnamespace123", + "logs-elastic_agent.filebeat-testnamespace123", + "logs-elastic_agent.fleet_server-testnamespace123", + "logs-elastic_agent.metricbeat-testnamespace123", + "logs-elastic_agent.osquerybeat-testnamespace123", + "logs-elastic_agent.packetbeat-testnamespace123", + "logs-elastic_agent.endpoint_security-testnamespace123", + "logs-elastic_agent.auditbeat-testnamespace123", + "logs-elastic_agent.heartbeat-testnamespace123", + "metrics-elastic_agent-testnamespace123", + "metrics-elastic_agent.elastic_agent-testnamespace123", + "metrics-elastic_agent.apm_server-testnamespace123", + "metrics-elastic_agent.filebeat-testnamespace123", + "metrics-elastic_agent.fleet_server-testnamespace123", + "metrics-elastic_agent.metricbeat-testnamespace123", + "metrics-elastic_agent.osquerybeat-testnamespace123", + "metrics-elastic_agent.packetbeat-testnamespace123", + "metrics-elastic_agent.endpoint_security-testnamespace123", + "metrics-elastic_agent.auditbeat-testnamespace123", + "metrics-elastic_agent.heartbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions Without elastic agent package installed should return default logs permissions if only logs are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "logs-elastic_agent-testnamespace123", + "logs-elastic_agent.elastic_agent-testnamespace123", + "logs-elastic_agent.apm_server-testnamespace123", + "logs-elastic_agent.filebeat-testnamespace123", + "logs-elastic_agent.fleet_server-testnamespace123", + "logs-elastic_agent.metricbeat-testnamespace123", + "logs-elastic_agent.osquerybeat-testnamespace123", + "logs-elastic_agent.packetbeat-testnamespace123", + "logs-elastic_agent.endpoint_security-testnamespace123", + "logs-elastic_agent.auditbeat-testnamespace123", + "logs-elastic_agent.heartbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; + +exports[`getMonitoringPermissions Without elastic agent package installed should return default metrics permissions if only metrics are enabled 1`] = ` +Object { + "_elastic_agent_monitoring": Object { + "indices": Array [ + Object { + "names": Array [ + "metrics-elastic_agent-testnamespace123", + "metrics-elastic_agent.elastic_agent-testnamespace123", + "metrics-elastic_agent.apm_server-testnamespace123", + "metrics-elastic_agent.filebeat-testnamespace123", + "metrics-elastic_agent.fleet_server-testnamespace123", + "metrics-elastic_agent.metricbeat-testnamespace123", + "metrics-elastic_agent.osquerybeat-testnamespace123", + "metrics-elastic_agent.packetbeat-testnamespace123", + "metrics-elastic_agent.endpoint_security-testnamespace123", + "metrics-elastic_agent.auditbeat-testnamespace123", + "metrics-elastic_agent.heartbeat-testnamespace123", + ], + "privileges": Array [ + "auto_configure", + "create_doc", + ], + }, + ], + }, +} +`; diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts index 8df1234982ee6..9a9b200d14130 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.test.ts @@ -13,7 +13,11 @@ import { agentPolicyService } from '../agent_policy'; import { agentPolicyUpdateEventHandler } from '../agent_policy_update'; import { getFullAgentPolicy } from './full_agent_policy'; +import { getMonitoringPermissions } from './monitoring_permissions'; +const mockedGetElasticAgentMonitoringPermissions = getMonitoringPermissions as jest.Mock< + ReturnType +>; const mockedAgentPolicyService = agentPolicyService as jest.Mocked; function mockAgentPolicy(data: Partial) { @@ -87,6 +91,8 @@ jest.mock('../agent_policy_update'); jest.mock('../agents'); jest.mock('../package_policy'); +jest.mock('./monitoring_permissions'); + function getAgentPolicyUpdateMock() { return agentPolicyUpdateEventHandler as unknown as jest.Mock< typeof agentPolicyUpdateEventHandler @@ -97,6 +103,29 @@ describe('getFullAgentPolicy', () => { beforeEach(() => { getAgentPolicyUpdateMock().mockClear(); mockedAgentPolicyService.get.mockReset(); + mockedGetElasticAgentMonitoringPermissions.mockReset(); + mockedGetElasticAgentMonitoringPermissions.mockImplementation( + async (soClient, { logs, metrics }, namespace) => { + const names: string[] = []; + if (logs) { + names.push(`logs-${namespace}`); + } + if (metrics) { + names.push(`metrics-${namespace}`); + } + + return { + _elastic_agent_monitoring: { + indices: [ + { + names, + privileges: [], + }, + ], + }, + }; + } + ); }); it('should return a policy without monitoring if monitoring is not enabled', async () => { @@ -200,6 +229,24 @@ describe('getFullAgentPolicy', () => { }); }); + it('should get the permissions for monitoring', async () => { + mockAgentPolicy({ + namespace: 'testnamespace', + revision: 1, + monitoring_enabled: ['metrics'], + }); + await getFullAgentPolicy(savedObjectsClientMock.create(), 'agent-policy'); + + expect(mockedGetElasticAgentMonitoringPermissions).toHaveBeenCalledWith( + expect.anything(), + { + logs: false, + metrics: true, + }, + 'testnamespace' + ); + }); + it('should support a different monitoring output', async () => { mockAgentPolicy({ namespace: 'default', diff --git a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts index 4e8b3a2c1952e..561c463b998d4 100644 --- a/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policies/full_agent_policy.ts @@ -24,21 +24,9 @@ import { import { storedPackagePoliciesToAgentInputs, dataTypes, outputType } from '../../../common'; import type { FullAgentPolicyOutputPermissions } from '../../../common'; import { getSettings } from '../settings'; -import { PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, DEFAULT_OUTPUT } from '../../constants'; - -const MONITORING_DATASETS = [ - 'elastic_agent', - 'elastic_agent.elastic_agent', - 'elastic_agent.apm_server', - 'elastic_agent.filebeat', - 'elastic_agent.fleet_server', - 'elastic_agent.metricbeat', - 'elastic_agent.osquerybeat', - 'elastic_agent.packetbeat', - 'elastic_agent.endpoint_security', - 'elastic_agent.auditbeat', - 'elastic_agent.heartbeat', -]; +import { DEFAULT_OUTPUT } from '../../constants'; + +import { getMonitoringPermissions } from './monitoring_permissions'; export async function getFullAgentPolicy( soClient: SavedObjectsClientContract, @@ -125,41 +113,17 @@ export async function getFullAgentPolicy( cluster: DEFAULT_PERMISSIONS.cluster, }; - // TODO: fetch this from the elastic agent package https://github.com/elastic/kibana/issues/107738 - const monitoringNamespace = fullAgentPolicy.agent?.monitoring.namespace; - const monitoringPermissions: FullAgentPolicyOutputPermissions = - monitoringOutputId === dataOutputId - ? dataPermissions - : { - _elastic_agent_checks: { - cluster: DEFAULT_PERMISSIONS.cluster, - }, - }; - if ( - fullAgentPolicy.agent?.monitoring.enabled && - monitoringNamespace && - monitoringOutput && - monitoringOutput.type === outputType.Elasticsearch - ) { - let names: string[] = []; - if (fullAgentPolicy.agent.monitoring.logs) { - names = names.concat( - MONITORING_DATASETS.map((dataset) => `logs-${dataset}-${monitoringNamespace}`) - ); - } - if (fullAgentPolicy.agent.monitoring.metrics) { - names = names.concat( - MONITORING_DATASETS.map((dataset) => `metrics-${dataset}-${monitoringNamespace}`) - ); - } - - monitoringPermissions._elastic_agent_checks.indices = [ - { - names, - privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, - }, - ]; - } + const monitoringPermissions = await getMonitoringPermissions( + soClient, + { + logs: agentPolicy.monitoring_enabled?.includes(dataTypes.Logs) ?? false, + metrics: agentPolicy.monitoring_enabled?.includes(dataTypes.Metrics) ?? false, + }, + agentPolicy.namespace + ); + monitoringPermissions._elastic_agent_checks = { + cluster: DEFAULT_PERMISSIONS.cluster, + }; // Only add permissions if output.type is "elasticsearch" fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< @@ -167,10 +131,16 @@ export async function getFullAgentPolicy( >((outputPermissions, outputId) => { const output = fullAgentPolicy.outputs[outputId]; if (output && output.type === outputType.Elasticsearch) { - outputPermissions[outputId] = - outputId === getOutputIdForAgentPolicy(dataOutput) - ? dataPermissions - : monitoringPermissions; + const permissions: FullAgentPolicyOutputPermissions = {}; + if (outputId === getOutputIdForAgentPolicy(monitoringOutput)) { + Object.assign(permissions, monitoringPermissions); + } + + if (outputId === getOutputIdForAgentPolicy(dataOutput)) { + Object.assign(permissions, dataPermissions); + } + + outputPermissions[outputId] = permissions; } return outputPermissions; }, {}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.test.ts b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.test.ts new file mode 100644 index 0000000000000..3d928bed0f661 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.test.ts @@ -0,0 +1,101 @@ +/* + * 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 { savedObjectsClientMock } from 'src/core/server/mocks'; + +import type { Installation, PackageInfo } from '../../types'; +import { getPackageInfo, getInstallation } from '../epm/packages'; + +import { getMonitoringPermissions } from './monitoring_permissions'; + +jest.mock('../epm/packages'); + +const mockedGetInstallation = getInstallation as jest.Mock>; +const mockedGetPackageInfo = getPackageInfo as jest.Mock>; + +describe('getMonitoringPermissions', () => { + describe('Without elastic agent package installed', () => { + it('should return default logs and metrics permissions if both are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default logs permissions if only logs are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: false }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default metrics permissions if only metrics are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: false, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + }); + + describe('With elastic agent package installed', () => { + beforeEach(() => { + // Mock a simplified elastic agent package with only 4 datastreams logs and metrics for filebeat and metricbeat + mockedGetInstallation.mockResolvedValue({ + name: 'elastic_agent', + version: '1.0.0', + } as Installation); + mockedGetPackageInfo.mockResolvedValue({ + data_streams: [ + { + type: 'logs', + dataset: 'elastic_agent.metricbeat', + }, + { + type: 'metrics', + dataset: 'elastic_agent.metricbeat', + }, + { + type: 'logs', + dataset: 'elastic_agent.filebeat', + }, + { + type: 'metrics', + dataset: 'elastic_agent.filebeat', + }, + ], + } as PackageInfo); + }); + it('should return default logs and metrics permissions if both are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default logs permissions if only logs are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: true, metrics: false }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + it('should return default metrics permissions if only metrics are enabled', async () => { + const permissions = await getMonitoringPermissions( + savedObjectsClientMock.create(), + { logs: false, metrics: true }, + 'testnamespace123' + ); + expect(permissions).toMatchSnapshot(); + }); + }); +}); diff --git a/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.ts b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.ts new file mode 100644 index 0000000000000..3533d829e1342 --- /dev/null +++ b/x-pack/plugins/fleet/server/services/agent_policies/monitoring_permissions.ts @@ -0,0 +1,91 @@ +/* + * 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 'kibana/server'; + +import { getPackageInfo, getInstallation } from '../epm/packages'; +import { getDataStreamPrivileges } from '../package_policies_to_agent_permissions'; +import { + PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + AGENT_POLICY_DEFAULT_MONITORING_DATASETS, +} from '../../constants'; +import type { FullAgentPolicyOutputPermissions } from '../../../common'; +import { FLEET_ELASTIC_AGENT_PACKAGE } from '../../../common'; +import { dataTypes } from '../../../common'; + +function buildDefault(enabled: { logs: boolean; metrics: boolean }, namespace: string) { + let names: string[] = []; + if (enabled.logs) { + names = names.concat( + AGENT_POLICY_DEFAULT_MONITORING_DATASETS.map((dataset) => `logs-${dataset}-${namespace}`) + ); + } + if (enabled.metrics) { + names = names.concat( + AGENT_POLICY_DEFAULT_MONITORING_DATASETS.map((dataset) => `metrics-${dataset}-${namespace}`) + ); + } + + return { + _elastic_agent_monitoring: { + indices: [ + { + names, + privileges: PACKAGE_POLICY_DEFAULT_INDEX_PRIVILEGES, + }, + ], + }, + }; +} + +export async function getMonitoringPermissions( + soClient: SavedObjectsClientContract, + enabled: { logs: boolean; metrics: boolean }, + namespace: string +): Promise { + const installation = await getInstallation({ + savedObjectsClient: soClient, + pkgName: FLEET_ELASTIC_AGENT_PACKAGE, + }); + + if (!installation) { + return buildDefault(enabled, namespace); + } + + const pkg = await getPackageInfo({ + savedObjectsClient: soClient, + pkgName: installation.name, + pkgVersion: installation.version, + }); + + if (!pkg.data_streams || pkg.data_streams.length === 0) { + return buildDefault(enabled, namespace); + } + + return { + _elastic_agent_monitoring: { + indices: pkg.data_streams + .map((ds) => { + if (ds.type === dataTypes.Logs && !enabled.logs) { + return; + } + if (ds.type === dataTypes.Metrics && !enabled.metrics) { + return; + } + return getDataStreamPrivileges(ds, namespace); + }) + .filter( + ( + i + ): i is { + names: string[]; + privileges: string[]; + } => typeof i !== 'undefined' + ), + }, + }; +}