diff --git a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts index e532a90f8839f..a2c1dcd83dd20 100644 --- a/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts +++ b/x-pack/plugins/fleet/common/services/full_agent_policy_to_yaml.ts @@ -16,6 +16,7 @@ const POLICY_KEYS_ORDER = [ 'dataset', 'type', 'outputs', + 'output_permissions', 'agent', 'inputs', 'enabled', 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 6e984b2d0b3da..f1137633062b8 100644 --- a/x-pack/plugins/fleet/common/types/models/agent_policy.ts +++ b/x-pack/plugins/fleet/common/types/models/agent_policy.ts @@ -59,6 +59,16 @@ export interface FullAgentPolicyInput { [key: string]: any; } +export interface FullAgentPolicyOutputPermissions { + [role: string]: { + cluster: string[]; + indices: Array<{ + names: string[]; + privileges: string[]; + }>; + }; +} + export interface FullAgentPolicy { id: string; outputs: { @@ -66,6 +76,9 @@ export interface FullAgentPolicy { [key: string]: any; }; }; + output_permissions?: { + [output: string]: FullAgentPolicyOutputPermissions; + }; fleet?: | { hosts: string[]; diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index 357b9150407ef..cea62f0394b48 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -655,7 +655,7 @@ class AgentPolicyService { id: agentPolicy.id, outputs: { // TEMPORARY as we only support a default output - ...[defaultOutput].reduce( + ...[defaultOutput].reduce( // eslint-disable-next-line @typescript-eslint/naming-convention (outputs, { config_yaml, name, type, hosts, ca_sha256, api_key }) => { const configJs = config_yaml ? safeLoad(config_yaml) : {}; @@ -675,7 +675,7 @@ class AgentPolicyService { return outputs; }, - {} as FullAgentPolicy['outputs'] + {} ), }, inputs: storedPackagePoliciesToAgentInputs(agentPolicy.package_policies as PackagePolicy[]), @@ -698,6 +698,26 @@ class AgentPolicyService { }), }; + // Only add permissions if output.type is "elasticsearch" + fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce< + NonNullable + >((permissions, 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'], + }, + ], + }; + } + return permissions; + }, {}); + // only add settings if not in standalone if (!standalone) { let settings: Settings; diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts index daa3992ce5844..12205f3110614 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.test.ts @@ -144,6 +144,9 @@ describe('test agent checkin new action services', () => { api_key: undefined, }, }, + output_permissions: { + default: { _fallback: { cluster: [], indices: [] } }, + }, inputs: [], }, }, @@ -159,6 +162,7 @@ describe('test agent checkin new action services', () => { id: 'policy1', inputs: [], outputs: { default: { api_key: 'MOCK_API_KEY', hosts: [], type: 'elasticsearch' } }, + output_permissions: { default: { _fallback: { cluster: [], indices: [] } } }, }, }, id: 'action1', @@ -213,7 +217,7 @@ describe('test agent checkin new action services', () => { ).toEqual(expectedResult); }); - it('should return CONNFIG_CHANGE and data.config for agent version <= 7.9', async () => { + it('should return CONFIG_CHANGE and data.config for agent version <= 7.9', async () => { const expectedResult = [ { agent_id: 'agent1', @@ -223,6 +227,7 @@ describe('test agent checkin new action services', () => { id: 'policy1', inputs: [], outputs: { default: { api_key: 'MOCK_API_KEY', hosts: [], type: 'elasticsearch' } }, + output_permissions: { default: { _fallback: { cluster: [], indices: [] } } }, }, }, id: 'action1', diff --git a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts index 8810dd6ff1263..8f0000413471f 100644 --- a/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts +++ b/x-pack/plugins/fleet/server/services/agents/checkin/state_new_actions.ts @@ -40,6 +40,7 @@ import { } from '../actions'; import { appContextService } from '../../app_context'; import { updateAgent } from '../crud'; +import type { FullAgentPolicy, FullAgentPolicyOutputPermissions } from '../../../../common'; import { toPromiseAbortable, AbortError, createRateLimiter } from './rxjs_utils'; @@ -113,14 +114,20 @@ async function getAgentDefaultOutputAPIKey( async function getOrCreateAgentDefaultOutputAPIKey( soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, - agent: Agent + agent: Agent, + permissions: FullAgentPolicyOutputPermissions ): Promise { const defaultAPIKey = await getAgentDefaultOutputAPIKey(soClient, esClient, agent); if (defaultAPIKey) { return defaultAPIKey; } - const outputAPIKey = await APIKeysService.generateOutputApiKey(soClient, 'default', agent.id); + const outputAPIKey = await APIKeysService.generateOutputApiKey( + soClient, + 'default', + agent.id, + permissions + ); await updateAgent(esClient, agent.id, { default_api_key: outputAPIKey.key, default_api_key_id: outputAPIKey.id, @@ -167,15 +174,18 @@ export async function createAgentActionFromPolicyAction( } ); + // agent <= 7.9 uses `data.config` instead of `data.policy` + const policyProp = 'policy' in newAgentAction.data ? 'policy' : 'config'; + + // TODO: The null assertion `!` is strictly correct for the current use case + // where the only output is `elasticsearch`, but this might change in the future. + const permissions = (newAgentAction.data[policyProp] as FullAgentPolicy).output_permissions! + .default; + // Mutate the policy to set the api token for this agent - const apiKey = await getOrCreateAgentDefaultOutputAPIKey(soClient, esClient, agent); - if (newAgentAction.data.policy) { - newAgentAction.data.policy.outputs.default.api_key = apiKey; - } - // BWC for agent <= 7.9 - else if (newAgentAction.data.config) { - newAgentAction.data.config.outputs.default.api_key = apiKey; - } + const apiKey = await getOrCreateAgentDefaultOutputAPIKey(soClient, esClient, agent, permissions); + + newAgentAction.data[policyProp].outputs.default.api_key = apiKey; return [newAgentAction]; } diff --git a/x-pack/plugins/fleet/server/services/api_keys/index.ts b/x-pack/plugins/fleet/server/services/api_keys/index.ts index bf229c829fda8..1f9e77821360c 100644 --- a/x-pack/plugins/fleet/server/services/api_keys/index.ts +++ b/x-pack/plugins/fleet/server/services/api_keys/index.ts @@ -8,6 +8,8 @@ import type { KibanaRequest } from 'src/core/server'; import type { SavedObjectsClientContract } from 'src/core/server'; +import type { FullAgentPolicyOutputPermissions } from '../../../common'; + import { createAPIKey } from './security'; export { invalidateAPIKeys } from './security'; @@ -16,20 +18,11 @@ export * from './enrollment_api_key'; export async function generateOutputApiKey( soClient: SavedObjectsClientContract, outputId: string, - agentId: string + agentId: string, + permissions: FullAgentPolicyOutputPermissions ): Promise<{ key: string; id: string }> { const name = `${agentId}:${outputId}`; - const key = await createAPIKey(soClient, name, { - 'fleet-output': { - cluster: ['monitor'], - index: [ - { - names: ['logs-*', 'metrics-*', 'traces-*', '.logs-endpoint.diagnostic.collection-*'], - privileges: ['auto_configure', 'create_doc'], - }, - ], - }, - }); + const key = await createAPIKey(soClient, name, permissions); if (!key) { throw new Error('Unable to create an output api key');