Skip to content
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
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/server/routes/agent/actions_handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
import type { ActionsService } from '../../services/agents';
import type { PostNewAgentActionResponse } from '../../../common/types/rest_spec';
import { defaultFleetErrorHandler } from '../../errors';
import { getCurrentNamespace } from '../../services/spaces/get_current_namespace';

export const postNewAgentActionHandlerBuilder = function (
actionsService: ActionsService
Expand All @@ -39,6 +40,7 @@ export const postNewAgentActionHandlerBuilder = function (
created_at: new Date().toISOString(),
...newAgentAction,
agents: [agent.id],
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we validate the agent is in the current namespace?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe the call to actionsService.getAgent already handles that. I've added an integration test to cover that case.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just checked what is done in actionsService.getAgent and it seems there is no namespace validation there.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct me if I'm wrong :)

  • actionsService.getAgent is defined here
  • the postNewAgentActionHandlerBuilder maps it to AgentService.getAgentById here
  • and getAgentById contains namespace validation here

I've tried it manually as well and I'm seeing 404 for agent ids in other spaces. Am I missing anything?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I missed that change in getAgentById 👍

namespaces: [getCurrentNamespace(soClient)],
});

const body: PostNewAgentActionResponse = {
Expand Down
15 changes: 10 additions & 5 deletions x-pack/plugins/fleet/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@ import { defaultFleetErrorHandler, FleetNotFoundError } from '../../errors';
import * as AgentService from '../../services/agents';
import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics';
import { getAgentStatusForAgentPolicy } from '../../services/agents';
import { isAgentInNamespace } from '../../services/agents/namespace';
import { isAgentInNamespace } from '../../services/spaces/agent_namespaces';
import { getCurrentNamespace } from '../../services/spaces/get_current_namespace';

function verifyNamespace(agent: Agent, namespace?: string) {
if (!isAgentInNamespace(agent, namespace)) {
Expand All @@ -61,7 +62,7 @@ export const getAgentHandler: FleetRequestHandler<
const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser;

let agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId);
verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace());
verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client));

if (request.query.withMetrics) {
agent = (await fetchAndAssignAgentMetrics(esClientCurrentUser, [agent]))[0];
Expand Down Expand Up @@ -91,7 +92,7 @@ export const deleteAgentHandler: FleetRequestHandler<

try {
const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId);
verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace());
verifyNamespace(agent, getCurrentNamespace(coreContext.savedObjects.client));

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

Expand Down Expand Up @@ -131,7 +132,7 @@ export const updateAgentHandler: FleetRequestHandler<

try {
const agent = await fleetContext.agentClient.asCurrentUser.getAgent(request.params.agentId);
verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace());
verifyNamespace(agent, getCurrentNamespace(soClient));

await AgentService.updateAgent(esClient, request.params.agentId, partialAgent);
const body = {
Expand Down Expand Up @@ -387,7 +388,11 @@ export const getActionStatusHandler: RequestHandler<
const esClient = coreContext.elasticsearch.client.asInternalUser;

try {
const actionStatuses = await AgentService.getActionStatuses(esClient, request.query);
const actionStatuses = await AgentService.getActionStatuses(
esClient,
request.query,
getCurrentNamespace(coreContext.savedObjects.client)
);
const body: GetActionStatusResponse = { items: actionStatuses };
return response.ok({ body });
} catch (error) {
Expand Down
12 changes: 4 additions & 8 deletions x-pack/plugins/fleet/server/routes/enrollment_api_key/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@

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 {
GetEnrollmentAPIKeysRequestSchema,
Expand All @@ -25,6 +24,7 @@ import * as APIKeyService from '../../services/api_keys';
import { agentPolicyService } from '../../services/agent_policy';
import { defaultFleetErrorHandler, AgentPolicyNotFoundError } from '../../errors';
import { appContextService } from '../../services';
import { getCurrentNamespace } from '../../services/spaces/get_current_namespace';

export const getEnrollmentApiKeysHandler: RequestHandler<
undefined,
Expand All @@ -40,9 +40,7 @@ export const getEnrollmentApiKeysHandler: RequestHandler<
page: request.query.page,
perPage: request.query.perPage,
kuery: request.query.kuery,
spaceId: useSpaceAwareness
? soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING
: undefined,
spaceId: useSpaceAwareness ? getCurrentNamespace(soClient) : undefined,
});
const body: GetEnrollmentAPIKeysResponse = {
list: items, // deprecated
Expand Down Expand Up @@ -96,8 +94,7 @@ export const deleteEnrollmentApiKeyHandler: RequestHandler<
const { useSpaceAwareness } = appContextService.getExperimentalFeatures();
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const currentNamespace =
coreContext.savedObjects.client.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING;
const currentNamespace = getCurrentNamespace(coreContext.savedObjects.client);
await APIKeyService.deleteEnrollmentApiKey(
esClient,
request.params.keyId,
Expand Down Expand Up @@ -126,8 +123,7 @@ export const getOneEnrollmentApiKeyHandler: RequestHandler<
try {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const currentNamespace =
coreContext.savedObjects.client.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING;
const currentNamespace = getCurrentNamespace(coreContext.savedObjects.client);
const { useSpaceAwareness } = appContextService.getExperimentalFeatures();

const apiKey = await APIKeyService.getEnrollmentAPIKey(
Expand Down
135 changes: 70 additions & 65 deletions x-pack/plugins/fleet/server/services/agents/action_status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
AGENT_POLICY_INDEX,
} from '../../../common';
import { appContextService } from '..';
import { addNamespaceFilteringToQuery } from '../spaces/query_namespaces_filtering';

/**
* Return current bulk actions.
Expand All @@ -35,10 +36,11 @@ import { appContextService } from '..';
*/
export async function getActionStatuses(
esClient: ElasticsearchClient,
options: ActionStatusOptions
options: ActionStatusOptions,
namespace?: string
): Promise<ActionStatus[]> {
const actionResults = await getActionResults(esClient, options);
const policyChangeActions = await getPolicyChangeActions(esClient, options);
const actionResults = await getActionResults(esClient, options, namespace);
const policyChangeActions = await getPolicyChangeActions(esClient, options, namespace);
const actionStatuses = [...actionResults, ...policyChangeActions]
.sort((a: ActionStatus, b: ActionStatus) => (b.creationTime > a.creationTime ? 1 : -1))
.slice(getPage(options), getPerPage(options));
Expand All @@ -47,9 +49,10 @@ export async function getActionStatuses(

async function getActionResults(
esClient: ElasticsearchClient,
options: ActionStatusOptions
options: ActionStatusOptions,
namespace?: string
): Promise<ActionStatus[]> {
const actions = await getActions(esClient, options);
const actions = await getActions(esClient, options, namespace);
const cancelledActions = await getCancelledActions(esClient);
let acks: any;

Expand Down Expand Up @@ -200,41 +203,41 @@ export function getPerPage(options: ActionStatusOptions) {

async function getActions(
esClient: ElasticsearchClient,
options: ActionStatusOptions
options: ActionStatusOptions,
namespace?: string
): Promise<ActionStatus[]> {
const query = {
bool: {
must_not: [
{
term: {
type: 'CANCEL',
},
},
],
...(options.date || options.latest
? {
filter: [
{
range: {
'@timestamp': {
// options.date overrides options.latest
gte: options.date ?? `now-${(options.latest ?? 0) / 1000}s/s`,
lte: options.date ? moment(options.date).add(1, 'days').toISOString() : 'now/s',
},
},
},
],
}
: {}),
},
};
const res = await esClient.search<FleetServerAgentAction>({
index: AGENT_ACTIONS_INDEX,
ignore_unavailable: true,
from: 0,
size: getPerPage(options),
query: {
bool: {
must_not: [
{
term: {
type: 'CANCEL',
},
},
],
...(options.date || options.latest
? {
filter: [
{
range: {
'@timestamp': {
// options.date overrides options.latest
gte: options.date ?? `now-${(options.latest ?? 0) / 1000}s/s`,
lte: options.date
? moment(options.date).add(1, 'days').toISOString()
: 'now/s',
},
},
},
],
}
: {}),
},
},
query: addNamespaceFilteringToQuery(query, namespace),
body: {
sort: [{ '@timestamp': 'desc' }],
},
Expand Down Expand Up @@ -340,49 +343,51 @@ export const hasRolloutPeriodPassed = (source: FleetServerAgentAction) =>

async function getPolicyChangeActions(
esClient: ElasticsearchClient,
options: ActionStatusOptions
options: ActionStatusOptions,
namespace?: string
): Promise<ActionStatus[]> {
// option.latest is used to fetch recent errors, which policy change actions do not contain
if (options.latest) {
return [];
}

const agentPoliciesRes = await esClient.search({
index: AGENT_POLICY_INDEX,
size: getPerPage(options),
query: {
bool: {
filter: [
{
range: {
revision_idx: {
gt: 1,
},
const query = {
bool: {
filter: [
{
range: {
revision_idx: {
gt: 1,
},
},
// This filter is for retrieving docs created by Kibana, as opposed to Fleet Server (coordinator_idx: 1).
// Note: the coordinator will be removed from Fleet Server (https://github.com/elastic/fleet-server/pull/3131),
// so this filter will eventually not be needed.
{
term: {
coordinator_idx: 0,
},
},
// This filter is for retrieving docs created by Kibana, as opposed to Fleet Server (coordinator_idx: 1).
// Note: the coordinator will be removed from Fleet Server (https://github.com/elastic/fleet-server/pull/3131),
// so this filter will eventually not be needed.
{
term: {
coordinator_idx: 0,
},
...(options.date
? [
{
range: {
'@timestamp': {
gte: options.date,
lte: moment(options.date).add(1, 'days').toISOString(),
},
},
...(options.date
? [
{
range: {
'@timestamp': {
gte: options.date,
lte: moment(options.date).add(1, 'days').toISOString(),
},
},
]
: []),
],
},
},
]
: []),
],
},
};
const agentPoliciesRes = await esClient.search({
index: AGENT_POLICY_INDEX,
size: getPerPage(options),
query: addNamespaceFilteringToQuery(query, namespace),
sort: [
{
'@timestamp': {
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/fleet/server/services/agents/agent_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ import type * as estypes from '@elastic/elasticsearch/lib/api/typesWithBodyKey';

import type { SortResults } from '@elastic/elasticsearch/lib/api/types';

import { DEFAULT_NAMESPACE_STRING } from '@kbn/core-saved-objects-utils-server';

import type { AgentStatus, ListWithKuery } from '../../types';
import type { Agent, GetAgentStatusResponse } from '../../../common/types';
import { getAuthzFromRequest } from '../security';
import { appContextService } from '../app_context';
import { FleetUnauthorizedError } from '../../errors';

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

import { getAgentsByKuery, getAgentById } from './crud';
import { getAgentStatusById, getAgentStatusForAgentPolicy } from './status';
import { getLatestAvailableAgentVersion } from './versions';
Expand Down Expand Up @@ -183,7 +183,7 @@ export class AgentServiceImpl implements AgentService {
this.internalEsClient,
soClient,
preflightCheck,
soClient.getCurrentNamespace() ?? DEFAULT_NAMESPACE_STRING
getCurrentNamespace(soClient)
);
}

Expand Down
8 changes: 6 additions & 2 deletions x-pack/plugins/fleet/server/services/agents/crud.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ import {
AgentNotFoundError,
FleetUnauthorizedError,
} from '../../errors';

import { auditLoggingService } from '../audit_logging';
import { isAgentInNamespace } from '../spaces/agent_namespaces';
import { getCurrentNamespace } from '../spaces/get_current_namespace';

import { searchHitToAgent, agentSOAttributesToFleetServerAgentDoc } from './helpers';

import { buildAgentStatusRuntimeField } from './build_status_runtime_field';
import { getLatestAvailableAgentVersion } from './versions';

Expand Down Expand Up @@ -406,6 +406,10 @@ export async function getAgentById(
throw new AgentNotFoundError(`Agent ${agentId} not found`);
}

if (!isAgentInNamespace(agentHit, getCurrentNamespace(soClient))) {
throw new AgentNotFoundError(`${agentHit.id} not found in namespace`);
}

return agentHit;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ import { AgentReassignmentError } from '../../errors';

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

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

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

import { getAgentsById, getAgentsByKuery, openPointInTime } from './crud';
import type { GetAgentsOptions } from '.';
import { UpdateAgentTagsActionRunner, updateTagsBatch } from './update_agent_tags_action_runner';
import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace';

export async function updateAgentTags(
soClient: SavedObjectsClientContract,
Expand All @@ -26,7 +29,7 @@ export async function updateAgentTags(
): Promise<{ actionId: string }> {
const outgoingErrors: Record<Agent['id'], Error> = {};
const givenAgents: Agent[] = [];
const currentNameSpace = soClient.getCurrentNamespace();
const currentNameSpace = getCurrentNamespace(soClient);

if ('agentIds' in options) {
const maybeAgents = await getAgentsById(esClient, soClient, options.agentIds);
Expand Down
Loading