[Fleet] Use elastic_agent package to build monitoring permissions for agent (#112730)

This commit is contained in:
Nicolas Chaulet 2021-10-04 13:36:12 -04:00 committed by GitHub
parent 693727663e
commit a4eab441c0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 484 additions and 98 deletions

View file

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

View file

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

View file

@ -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 [],
},
],
},

View file

@ -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",
],
},
],
},
}
`;

View file

@ -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<typeof getMonitoringPermissions>
>;
const mockedAgentPolicyService = agentPolicyService as jest.Mocked<typeof agentPolicyService>;
function mockAgentPolicy(data: Partial<AgentPolicy>) {
@ -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',

View file

@ -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';
import { 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 { 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;
}, {});

View file

@ -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<ReturnType<typeof getInstallation>>;
const mockedGetPackageInfo = getPackageInfo as jest.Mock<ReturnType<typeof getPackageInfo>>;
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();
});
});
});

View file

@ -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<FullAgentPolicyOutputPermissions> {
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'
),
},
};
}