Skip to content

Commit

Permalink
[Fleet] Expose permissions as part of the agent policy (#94591)
Browse files Browse the repository at this point in the history
  • Loading branch information
Alejandro Fernández Gómez authored Mar 30, 2021
1 parent f7caefd commit 9931b8b
Show file tree
Hide file tree
Showing 6 changed files with 67 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const POLICY_KEYS_ORDER = [
'dataset',
'type',
'outputs',
'output_permissions',
'agent',
'inputs',
'enabled',
Expand Down
13 changes: 13 additions & 0 deletions x-pack/plugins/fleet/common/types/models/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,26 @@ 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: {
[key: string]: Pick<Output, 'type' | 'hosts' | 'ca_sha256' | 'api_key'> & {
[key: string]: any;
};
};
output_permissions?: {
[output: string]: FullAgentPolicyOutputPermissions;
};
fleet?:
| {
hosts: string[];
Expand Down
24 changes: 22 additions & 2 deletions x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -694,7 +694,7 @@ class AgentPolicyService {
id: agentPolicy.id,
outputs: {
// TEMPORARY as we only support a default output
...[defaultOutput].reduce(
...[defaultOutput].reduce<FullAgentPolicy['outputs']>(
// 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) : {};
Expand All @@ -714,7 +714,7 @@ class AgentPolicyService {

return outputs;
},
{} as FullAgentPolicy['outputs']
{}
),
},
inputs: storedPackagePoliciesToAgentInputs(agentPolicy.package_policies as PackagePolicy[]),
Expand All @@ -737,6 +737,26 @@ class AgentPolicyService {
}),
};

// Only add permissions if output.type is "elasticsearch"
fullAgentPolicy.output_permissions = Object.keys(fullAgentPolicy.outputs).reduce<
NonNullable<FullAgentPolicy['output_permissions']>
>((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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,9 @@ describe('test agent checkin new action services', () => {
api_key: undefined,
},
},
output_permissions: {
default: { _fallback: { cluster: [], indices: [] } },
},
inputs: [],
},
},
Expand All @@ -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',
Expand Down Expand Up @@ -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',
Expand All @@ -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',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down Expand Up @@ -113,14 +114,20 @@ async function getAgentDefaultOutputAPIKey(
async function getOrCreateAgentDefaultOutputAPIKey(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agent: Agent
agent: Agent,
permissions: FullAgentPolicyOutputPermissions
): Promise<string> {
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,
Expand Down Expand Up @@ -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];
}
Expand Down
17 changes: 5 additions & 12 deletions x-pack/plugins/fleet/server/services/api_keys/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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');
Expand Down

0 comments on commit 9931b8b

Please sign in to comment.