Skip to content

Commit

Permalink
Merge branch 'main' into szaffarano/license-info-enrichment
Browse files Browse the repository at this point in the history
  • Loading branch information
szaffarano authored Jul 25, 2024
2 parents c08ab24 + 9e71c7e commit 27eb39b
Show file tree
Hide file tree
Showing 10 changed files with 458 additions and 21 deletions.
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/common/types/models/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ export interface NewAgentAction {
ack_data?: any;
sent_at?: string;
agents: string[];
namespaces?: string[];
created_at?: string;
id?: string;
expiration?: string;
Expand Down Expand Up @@ -408,6 +409,8 @@ export interface FleetServerAgentAction {
*/
agents?: string[];

namespaces?: string[];

/**
* Date when the agent should execute that agent. This field could be altered by Fleet server for progressive rollout of the action.
*/
Expand Down
33 changes: 14 additions & 19 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 Down Expand Up @@ -45,19 +44,10 @@ import { defaultFleetErrorHandler, FleetNotFoundError } from '../../errors';
import * as AgentService from '../../services/agents';
import { fetchAndAssignAgentMetrics } from '../../services/agents/agent_metrics';
import { getAgentStatusForAgentPolicy } from '../../services/agents';
import { appContextService } from '../../services';
import { isAgentInNamespace } from '../../services/agents/namespace';

export function verifyNamespace(agent: Agent, currentNamespace?: string) {
if (!appContextService.getExperimentalFeatures().useSpaceAwareness) {
return;
}
const isInNamespace =
(currentNamespace && agent.namespaces?.includes(currentNamespace)) ||
(!currentNamespace &&
(!agent.namespaces ||
agent.namespaces.length === 0 ||
agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING)));
if (!isInNamespace) {
function verifyNamespace(agent: Agent, namespace?: string) {
if (!isAgentInNamespace(agent, namespace)) {
throw new FleetNotFoundError(`${agent.id} not found in namespace`);
}
}
Expand All @@ -71,7 +61,6 @@ 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());

if (request.query.withMetrics) {
Expand All @@ -94,12 +83,15 @@ 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);
verifyNamespace(agent, coreContext.savedObjects.client.getCurrentNamespace());

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

Expand All @@ -120,12 +112,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 @@ -138,6 +130,9 @@ export const updateAgentHandler: RequestHandler<
}

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

await AgentService.updateAgent(esClient, request.params.agentId, partialAgent);
const body = {
item: await AgentService.getAgentById(esClient, soClient, request.params.agentId),
Expand Down
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/server/services/agents/actions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export async function createAgentAction(
? undefined
: newAgentAction.expiration ?? new Date(now + ONE_MONTH_IN_MS).toISOString(),
agents: newAgentAction.agents,
namespaces: newAgentAction.namespaces,
action_id: actionId,
data: newAgentAction.data,
type: newAgentAction.type,
Expand Down Expand Up @@ -182,6 +183,7 @@ export async function bulkCreateAgentActionResults(
results: Array<{
actionId: string;
agentId: string;
namespaces?: string[];
error?: string;
}>
): Promise<void> {
Expand All @@ -194,6 +196,7 @@ export async function bulkCreateAgentActionResults(
'@timestamp': new Date().toISOString(),
action_id: result.actionId,
agent_id: result.agentId,
namespaces: result.namespaces,
error: result.error,
};

Expand Down
119 changes: 119 additions & 0 deletions x-pack/plugins/fleet/server/services/agents/namespace.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/*
* 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 { appContextService } from '../app_context';

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

import { agentsKueryNamespaceFilter, isAgentInNamespace } from './namespace';

jest.mock('../app_context');

const mockedAppContextService = appContextService as jest.Mocked<typeof appContextService>;

describe('isAgentInNamespace', () => {
describe('with the useSpaceAwareness feature flag disabled', () => {
beforeEach(() => {
mockedAppContextService.getExperimentalFeatures.mockReturnValue({
useSpaceAwareness: false,
} as any);
});

it('returns true even if the agent is in a different space', () => {
const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent;
expect(isAgentInNamespace(agent, 'space2')).toEqual(true);
});
});

describe('with the useSpaceAwareness feature flag enabled', () => {
beforeEach(() => {
mockedAppContextService.getExperimentalFeatures.mockReturnValue({
useSpaceAwareness: true,
} as any);
});

describe('when the namespace is defined', () => {
it('returns true if the agent namespaces include the namespace', () => {
const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent;
expect(isAgentInNamespace(agent, 'space1')).toEqual(true);
});

it('returns false if the agent namespaces do not include the namespace', () => {
const agent = { id: '123', namespaces: ['default', 'space1'] } as Agent;
expect(isAgentInNamespace(agent, 'space2')).toEqual(false);
});

it('returns false if the agent has zero length namespaces', () => {
const agent = { id: '123', namespaces: [] as string[] } as Agent;
expect(isAgentInNamespace(agent, 'space1')).toEqual(false);
});

it('returns false if the agent does not have namespaces', () => {
const agent = { id: '123' } as Agent;
expect(isAgentInNamespace(agent, 'space1')).toEqual(false);
});
});

describe('when the namespace is undefined', () => {
it('returns true if the agent does not have namespaces', () => {
const agent = { id: '123' } as Agent;
expect(isAgentInNamespace(agent)).toEqual(true);
});

it('returns true if the agent has zero length namespaces', () => {
const agent = { id: '123', namespaces: [] as string[] } as Agent;
expect(isAgentInNamespace(agent)).toEqual(true);
});

it('returns true if the agent namespaces include the default one', () => {
const agent = { id: '123', namespaces: ['default'] } as Agent;
expect(isAgentInNamespace(agent)).toEqual(true);
});

it('returns false if the agent namespaces include the default one', () => {
const agent = { id: '123', namespaces: ['space1'] } as Agent;
expect(isAgentInNamespace(agent)).toEqual(false);
});
});
});
});

describe('agentsKueryNamespaceFilter', () => {
describe('with the useSpaceAwareness feature flag disabled', () => {
beforeEach(() => {
mockedAppContextService.getExperimentalFeatures.mockReturnValue({
useSpaceAwareness: false,
} as any);
});

it('returns undefined if the useSpaceAwareness feature flag disabled', () => {
expect(agentsKueryNamespaceFilter('space1')).toBeUndefined();
});
});

describe('with the useSpaceAwareness feature flag enabled', () => {
beforeEach(() => {
mockedAppContextService.getExperimentalFeatures.mockReturnValue({
useSpaceAwareness: true,
} as any);
});

it('returns undefined if the namespace is undefined', () => {
expect(agentsKueryNamespaceFilter()).toBeUndefined();
});

it('returns a kuery for the default space', () => {
expect(agentsKueryNamespaceFilter('default')).toEqual(
'namespaces:(default) or not namespaces:*'
);
});

it('returns a kuery for custom spaces', () => {
expect(agentsKueryNamespaceFilter('space1')).toEqual('namespaces:(space1)');
});
});
});
37 changes: 37 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,37 @@
/*
* 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 { appContextService } from '../app_context';

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

export function isAgentInNamespace(agent: Agent, namespace?: string) {
const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness;
if (!useSpaceAwareness) {
return true;
}

return (
(namespace && agent.namespaces?.includes(namespace)) ||
(!namespace &&
(!agent.namespaces ||
agent.namespaces.length === 0 ||
agent.namespaces?.includes(DEFAULT_NAMESPACE_STRING)))
);
}

export function agentsKueryNamespaceFilter(namespace?: string) {
const useSpaceAwareness = appContextService.getExperimentalFeatures()?.useSpaceAwareness;
if (!useSpaceAwareness || !namespace) {
return;
}
return namespace === DEFAULT_NAMESPACE_STRING
? `namespaces:(${DEFAULT_NAMESPACE_STRING}) or not namespaces:*`
: `namespaces:(${namespace})`;
}
Loading

0 comments on commit 27eb39b

Please sign in to comment.