Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] RBAC - Make agents write APIs space aware - 4/4 #191277

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export const requestDiagnosticsHandler: RequestHandler<

const result = await AgentService.requestDiagnostics(
esClient,
soClient,
request.params.agentId,
request.body?.additional_metrics
);
Expand Down
8 changes: 3 additions & 5 deletions x-pack/plugins/fleet/server/services/agents/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -313,7 +313,7 @@ export async function cancelAgentAction(
soClient: SavedObjectsClientContract,
actionId: string
) {
const currentNameSpace = getCurrentNamespace(soClient);
const currentSpaceId = getCurrentNamespace(soClient);

const getUpgradeActions = async () => {
const query = {
Expand All @@ -329,7 +329,7 @@ export async function cancelAgentAction(
};
const res = await esClient.search<FleetServerAgentAction>({
index: AGENT_ACTIONS_INDEX,
query: await addNamespaceFilteringToQuery(query, currentNameSpace),
query: await addNamespaceFilteringToQuery(query, currentSpaceId),
size: SO_SEARCH_LIMIT,
});

Expand Down Expand Up @@ -358,12 +358,10 @@ export async function cancelAgentAction(
const cancelledActions: Array<{ agents: string[] }> = [];

const createAction = async (action: FleetServerAgentAction) => {
const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {};

await createAgentAction(esClient, {
id: cancelActionId,
type: 'CANCEL',
...namespaces,
namespaces: [currentSpaceId],
agents: action.agents!,
data: {
target_id: action.action_id,
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/fleet/server/services/agents/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -433,7 +433,7 @@ async function _filterAgents(
}> {
const { page = 1, perPage = 20, sortField = 'enrolled_at', sortOrder = 'desc' } = options;
const runtimeFields = await buildAgentStatusRuntimeField(soClient);
const currentNameSpace = getCurrentNamespace(soClient);
const currentSpaceId = getCurrentNamespace(soClient);

let res;
try {
Expand All @@ -445,7 +445,7 @@ async function _filterAgents(
runtime_mappings: runtimeFields,
fields: Object.keys(runtimeFields),
sort: [{ [sortField]: { order: sortOrder } }],
query: await addNamespaceFilteringToQuery({ bool: { filter: [query] } }, currentNameSpace),
query: await addNamespaceFilteringToQuery({ bool: { filter: [query] } }, currentSpaceId),
index: AGENTS_INDEX,
ignore_unavailable: true,
});
Expand Down
13 changes: 6 additions & 7 deletions x-pack/plugins/fleet/server/services/agents/reassign.ts
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,7 @@ export async function reassignAgent(
policy_revision: null,
});

const currentNameSpace = getCurrentNamespace(soClient);
const namespaces = currentNameSpace ? { namespaces: [currentNameSpace] } : {};
const currentSpaceId = getCurrentNamespace(soClient);

await createAgentAction(esClient, {
agents: [agentId],
Expand All @@ -88,7 +87,7 @@ export async function reassignAgent(
data: {
policy_id: newAgentPolicyId,
},
...namespaces,
namespaces: [currentSpaceId],
});
}

Expand All @@ -103,7 +102,7 @@ export async function reassignAgents(
): Promise<{ actionId: string }> {
await verifyNewAgentPolicy(soClient, newAgentPolicyId);

const currentNameSpace = getCurrentNamespace(soClient);
const currentSpaceId = getCurrentNamespace(soClient);
const outgoingErrors: Record<Agent['id'], Error> = {};
let givenAgents: Agent[] = [];
if ('agents' in options) {
Expand All @@ -121,7 +120,7 @@ export async function reassignAgents(
}
} else if ('kuery' in options) {
const batchSize = options.batchSize ?? SO_SEARCH_LIMIT;
const namespaceFilter = await agentsKueryNamespaceFilter(currentNameSpace);
const namespaceFilter = await agentsKueryNamespaceFilter(currentSpaceId);
const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery;
const res = await getAgentsByKuery(esClient, soClient, {
kuery,
Expand All @@ -138,7 +137,7 @@ export async function reassignAgents(
soClient,
{
...options,
spaceId: currentNameSpace,
spaceId: currentSpaceId,
batchSize,
total: res.total,
newAgentPolicyId,
Expand All @@ -150,7 +149,7 @@ export async function reassignAgents(

return await reassignBatch(
esClient,
{ newAgentPolicyId, spaceId: currentNameSpace },
{ newAgentPolicyId, spaceId: currentSpaceId },
givenAgents,
outgoingErrors
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ export async function reassignBatch(
const actionId = options.actionId ?? uuidv4();
const total = options.total ?? givenAgents.length;
const now = new Date().toISOString();
const namespaces = spaceId ? { namespaces: [spaceId] } : {};
const namespaces = spaceId ? [spaceId] : [];

await createAgentAction(esClient, {
id: actionId,
Expand All @@ -100,7 +100,7 @@ export async function reassignBatch(
data: {
policy_id: options.newAgentPolicyId,
},
...namespaces,
namespaces,
});

await createErrorActionResults(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ describe('requestDiagnostics', () => {

describe('requestDiagnostics (singular)', () => {
it('can request diagnostics for single agent', async () => {
const { esClient, agentInRegularDoc } = createClientMock();
await requestDiagnostics(esClient, agentInRegularDoc._id);
const { soClient, esClient, agentInRegularDoc } = createClientMock();
await requestDiagnostics(esClient, soClient, agentInRegularDoc._id);

expect(esClient.create).toHaveBeenCalledWith(
expect.objectContaining({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import type { RequestDiagnosticsAdditionalMetrics } from '../../../common/types'

import { SO_SEARCH_LIMIT, REQUEST_DIAGNOSTICS_TIMEOUT_MS } from '../../constants';

import { getCurrentNamespace } from '../spaces/get_current_namespace';

import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces';

import type { GetAgentsOptions } from '.';
import { getAgents, getAgentsByKuery } from './crud';
import { createAgentAction } from './actions';
Expand All @@ -22,9 +26,12 @@ import {

export async function requestDiagnostics(
esClient: ElasticsearchClient,
soClient: SavedObjectsClientContract,
agentId: string,
additionalMetrics?: RequestDiagnosticsAdditionalMetrics[]
): Promise<{ actionId: string }> {
const currentSpaceId = getCurrentNamespace(soClient);

const response = await createAgentAction(esClient, {
agents: [agentId],
created_at: new Date().toISOString(),
Expand All @@ -33,6 +40,7 @@ export async function requestDiagnostics(
data: {
additional_metrics: additionalMetrics,
},
namespaces: [currentSpaceId],
});
return { actionId: response.id };
}
Expand All @@ -45,24 +53,29 @@ export async function bulkRequestDiagnostics(
additionalMetrics?: RequestDiagnosticsAdditionalMetrics[];
}
): Promise<{ actionId: string }> {
const currentSpaceId = getCurrentNamespace(soClient);

if ('agentIds' in options) {
const givenAgents = await getAgents(esClient, soClient, options);
return await requestDiagnosticsBatch(esClient, givenAgents, {
additionalMetrics: options.additionalMetrics,
spaceId: currentSpaceId,
});
}

const batchSize = options.batchSize ?? SO_SEARCH_LIMIT;
const namespaceFilter = await agentsKueryNamespaceFilter(currentSpaceId);
const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery;
const res = await getAgentsByKuery(esClient, soClient, {
kuery: options.kuery,
kuery,
showInactive: false,
page: 1,
perPage: batchSize,
});
if (res.total <= batchSize) {
const givenAgents = await getAgents(esClient, soClient, options);
return await requestDiagnosticsBatch(esClient, givenAgents, {
return await requestDiagnosticsBatch(esClient, res.agents, {
additionalMetrics: options.additionalMetrics,
spaceId: currentSpaceId,
});
} else {
return await new RequestDiagnosticsActionRunner(
Expand All @@ -72,6 +85,7 @@ export async function bulkRequestDiagnostics(
...options,
batchSize,
total: res.total,
spaceId: currentSpaceId,
},
{ pitId: await openPointInTime(esClient) }
).runActionAsyncWithRetry();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export async function requestDiagnosticsBatch(
actionId?: string;
total?: number;
additionalMetrics?: RequestDiagnosticsAdditionalMetrics[];
spaceId?: string;
}
): Promise<{ actionId: string }> {
const errors: Record<Agent['id'], Error> = {};
Expand All @@ -56,6 +57,8 @@ export async function requestDiagnosticsBatch(
});

const agentIds = givenAgents.map((agent) => agent.id);
const spaceId = options.spaceId;
const namespaces = spaceId ? [spaceId] : [];

await createAgentAction(esClient, {
id: actionId,
Expand All @@ -67,6 +70,7 @@ export async function requestDiagnosticsBatch(
data: {
additional_metrics: options.additionalMetrics,
},
namespaces,
});

await createErrorActionResults(
Expand Down
25 changes: 20 additions & 5 deletions x-pack/plugins/fleet/server/services/agents/unenroll.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ import { v4 as uuidv4 } from 'uuid';
import type { Agent } from '../../types';
import { HostedAgentPolicyRestrictionRelatedError } from '../../errors';
import { SO_SEARCH_LIMIT } from '../../constants';
import { getCurrentNamespace } from '../spaces/get_current_namespace';
import { agentsKueryNamespaceFilter } from '../spaces/agent_namespaces';

import { createAgentAction } from './actions';
import type { GetAgentsOptions } from './crud';
Expand Down Expand Up @@ -49,17 +51,20 @@ export async function unenrollAgent(
revoke?: boolean;
}
) {
await getAgentById(esClient, soClient, agentId); // throw 404 if agent not in namespace
if (!options?.force) {
await unenrollAgentIsAllowed(soClient, esClient, agentId);
}
if (options?.revoke) {
return forceUnenrollAgent(esClient, soClient, agentId);
}
const now = new Date().toISOString();
const currentSpaceId = getCurrentNamespace(soClient);
await createAgentAction(esClient, {
agents: [agentId],
created_at: now,
type: 'UNENROLL',
namespaces: [currentSpaceId],
});
await updateAgent(esClient, agentId, {
unenrollment_started_at: now,
Expand All @@ -76,27 +81,37 @@ export async function unenrollAgents(
showInactive?: boolean;
}
): Promise<{ actionId: string }> {
const spaceId = getCurrentNamespace(soClient);

if ('agentIds' in options) {
const givenAgents = await getAgents(esClient, soClient, options);
return await unenrollBatch(soClient, esClient, givenAgents, options);
return await unenrollBatch(soClient, esClient, givenAgents, {
...options,
spaceId,
});
}

const batchSize = options.batchSize ?? SO_SEARCH_LIMIT;
const namespaceFilter = await agentsKueryNamespaceFilter(spaceId);
const kuery = namespaceFilter ? `${namespaceFilter} AND ${options.kuery}` : options.kuery;
const res = await getAgentsByKuery(esClient, soClient, {
kuery: options.kuery,
kuery,
showInactive: options.showInactive ?? false,
page: 1,
perPage: batchSize,
});
if (res.total <= batchSize) {
const givenAgents = await getAgents(esClient, soClient, options);
return await unenrollBatch(soClient, esClient, givenAgents, options);
return await unenrollBatch(soClient, esClient, res.agents, {
...options,
spaceId,
});
} else {
return await new UnenrollActionRunner(
esClient,
soClient,
{
...options,
spaceId,
batchSize,
total: res.total,
},
Expand All @@ -120,5 +135,5 @@ export async function forceUnenrollAgent(
active: false,
unenrolled_at: new Date().toISOString(),
});
await updateActionsForForceUnenroll(esClient, [agent.id], uuidv4(), 1);
await updateActionsForForceUnenroll(esClient, soClient, [agent.id], uuidv4(), 1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import { invalidateAPIKeys } from '../api_keys';

import { appContextService } from '../app_context';

import { getCurrentNamespace } from '../spaces/get_current_namespace';

import { ActionRunner } from './action_runner';

import { bulkUpdateAgents } from './crud';
Expand Down Expand Up @@ -61,6 +63,7 @@ export async function unenrollBatch(
revoke?: boolean;
actionId?: string;
total?: number;
spaceId?: string;
}
): Promise<{ actionId: string }> {
const hostedPolicies = await getHostedPolicies(soClient, givenAgents);
Expand Down Expand Up @@ -100,11 +103,14 @@ export async function unenrollBatch(

const agentIds = agentsToUpdate.map((agent) => agent.id);

const spaceId = options.spaceId;
const namespaces = spaceId ? [spaceId] : [];

if (options.revoke) {
// Get all API keys that need to be invalidated
await invalidateAPIKeysForAgents(agentsToUpdate);

await updateActionsForForceUnenroll(esClient, agentIds, actionId, total);
await updateActionsForForceUnenroll(esClient, soClient, agentIds, actionId, total);
} else {
// Create unenroll action for each agent
await createAgentAction(esClient, {
Expand All @@ -113,6 +119,7 @@ export async function unenrollBatch(
created_at: now,
type: 'UNENROLL',
total,
namespaces,
});
}

Expand All @@ -130,17 +137,20 @@ export async function unenrollBatch(

export async function updateActionsForForceUnenroll(
esClient: ElasticsearchClient,
soClient: SavedObjectsClientContract,
agentIds: string[],
actionId: string,
total: number
) {
// creating an action doc so that force unenroll shows up in activity
const currentSpaceId = getCurrentNamespace(soClient);
await createAgentAction(esClient, {
id: actionId,
agents: agentIds,
created_at: new Date().toISOString(),
type: 'FORCE_UNENROLL',
total,
namespaces: [currentSpaceId],
});
await bulkCreateAgentActionResults(
esClient,
Expand Down
Loading
Loading