Skip to content

Commit

Permalink
[Fleet] RBAC - Make agents write APIs space aware
Browse files Browse the repository at this point in the history
  • Loading branch information
jillguyonnet committed Jul 17, 2024
1 parent 23732e6 commit 55389b2
Show file tree
Hide file tree
Showing 4 changed files with 92 additions and 20 deletions.
39 changes: 19 additions & 20 deletions x-pack/plugins/fleet/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import { uniq } from 'lodash';
import { type RequestHandler, SavedObjectsErrorHelpers } from '@kbn/core/server';
import type { TypeOf } from '@kbn/config-schema';
import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';

import type {
GetAgentsResponse,
Expand All @@ -21,7 +20,6 @@ import type {
GetAgentUploadsResponse,
PostAgentReassignResponse,
PostRetrieveAgentsByActionsResponse,
Agent,
} from '../../../common/types';
import type {
GetAgentsRequestSchema,
Expand All @@ -45,18 +43,7 @@ import { defaultFleetErrorHandler, FleetNotFoundError } from '../../errors';
import * as AgentService from '../../services/agents';
import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics';
import { getAgentStatusForAgentPolicy } from '../../services/agents';

export function verifyNamespace(agent: Agent, currentNamespace?: string) {
const isInNamespace =
(currentNamespace && agent.namespaces?.includes(currentNamespace)) ||
(!currentNamespace &&
(!agent.namespaces ||
agent.namespaces.length === 0 ||
agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING)));
if (!isInNamespace) {
throw new FleetNotFoundError(`${agent.id} not found in namespace`);
}
}
import { isAgentInNamespace } from '../../services/agents/namespace';

export const getAgentHandler: FleetRequestHandler<
TypeOf<typeof GetOneAgentRequestSchema.params>,
Expand All @@ -68,7 +55,9 @@ export const getAgentHandler: FleetRequestHandler<

let agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId);

verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace());
if (!isAgentInNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace())) {
throw new FleetNotFoundError(`${agent.id} not found in namespace`);
}

if (request.query.withMetrics) {
agent = (await fetchAndAssignAgentMetrics(esClientCurrentUser, [agent]))[0];
Expand All @@ -90,12 +79,17 @@ export const getAgentHandler: FleetRequestHandler<
}
};

export const deleteAgentHandler: RequestHandler<
export const deleteAgentHandler: FleetRequestHandler<
TypeOf<typeof DeleteAgentRequestSchema.params>
> = async (context, request, response) => {
const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]);
const esClient = coreContext.elasticsearch.client.asInternalUser;

try {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId);
if (!isAgentInNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace())) {
throw new FleetNotFoundError(`${agent.id} not found in namespace`);
}

await AgentService.deleteAgent(esClient, request.params.agentId);

Expand All @@ -116,12 +110,12 @@ export const deleteAgentHandler: RequestHandler<
}
};

export const updateAgentHandler: RequestHandler<
export const updateAgentHandler: FleetRequestHandler<
TypeOf<typeof UpdateAgentRequestSchema.params>,
undefined,
TypeOf<typeof UpdateAgentRequestSchema.body>
> = async (context, request, response) => {
const coreContext = await context.core;
const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]);
const esClient = coreContext.elasticsearch.client.asInternalUser;
const soClient = coreContext.savedObjects.client;

Expand All @@ -134,6 +128,11 @@ export const updateAgentHandler: RequestHandler<
}

try {
const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId);
if (!isAgentInNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace())) {
throw new FleetNotFoundError(`${agent.id} not found in namespace`);
}

await AgentService.updateAgent(esClient, request.params.agentId, partialAgent);
const body = {
item: await AgentService.getAgentById(esClient, soClient, request.params.agentId),
Expand Down
20 changes: 20 additions & 0 deletions x-pack/plugins/fleet/server/services/agents/namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* 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 { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';

import type { Agent } from '../../types';

export function isAgentInNamespace(agent: Agent, namespace?: string) {
return (
(namespace && agent.namespaces?.includes(namespace)) ||
(!namespace &&
(!agent.namespaces ||
agent.namespaces.length === 0 ||
agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING)))
);
}
36 changes: 36 additions & 0 deletions x-pack/test/fleet_api_integration/apis/space_awareness/agents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,5 +132,41 @@ export default function (providerContext: FtrProviderContext) {
expect(err?.message).to.match(/404 "Not Found"/);
});
});

describe('PUT /agents/{id}', () => {
it('should allow to update an agent in the same space', async () => {
await apiClient.updateAgent(testSpaceAgent1, { tags: ['foo'] }, TEST_SPACE_1);
});

it('should not allow to update an agent from a different space from the default space', async () => {
let err: Error | undefined;
try {
await apiClient.updateAgent(testSpaceAgent1, { tags: ['foo'] });
} catch (_err) {
err = _err;
}

expect(err).to.be.an(Error);
expect(err?.message).to.match(/404 "Not Found"/);
});
});

describe('DELETE /agents/{id}', () => {
it('should allow to delete an agent in the same space', async () => {
await apiClient.deleteAgent(testSpaceAgent1, TEST_SPACE_1);
});

it('should not allow to delete an agent from a different space from the default space', async () => {
let err: Error | undefined;
try {
await apiClient.deleteAgent(testSpaceAgent1);
} catch (_err) {
err = _err;
}

expect(err).to.be.an(Error);
expect(err?.message).to.match(/404 "Not Found"/);
});
});
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,23 @@ export class SpaceTestApiClient {

return res;
}
async updateAgent(agentId: string, data: any, spaceId?: string) {
const { body: res } = await this.supertest
.put(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}`)
.set('kbn-xsrf', 'xxxx')
.send(data)
.expect(200);

return res;
}
async deleteAgent(agentId: string, spaceId?: string) {
const { body: res } = await this.supertest
.delete(`${this.getBaseUrl(spaceId)}/api/fleet/agents/${agentId}`)
.set('kbn-xsrf', 'xxxx')
.expect(200);

return res;
}
// Enrollment Settings
async getEnrollmentSettings(spaceId?: string): Promise<GetEnrollmentSettingsResponse> {
const { body: res } = await this.supertest
Expand Down

0 comments on commit 55389b2

Please sign in to comment.