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] Filter space aware entities in Fleet UI/API #184869

Merged
merged 21 commits into from
Jun 17, 2024
Merged
Show file tree
Hide file tree
Changes from 14 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: 1 addition & 1 deletion .buildkite/ftr_configs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -246,6 +246,7 @@ enabled:
- x-pack/test/fleet_api_integration/config.epm.ts
- x-pack/test/fleet_api_integration/config.fleet.ts
- x-pack/test/fleet_api_integration/config.package_policy.ts
- x-pack/test/fleet_api_integration/config.space_awareness.ts
- x-pack/test/fleet_functional/config.ts
- x-pack/test/ftr_apis/security_and_spaces/config.ts
- x-pack/test/functional_basic/apps/ml/permissions/config.ts
Expand Down Expand Up @@ -568,4 +569,3 @@ enabled:
- x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/configs/serverless.integrations.config.ts
- x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/configs/serverless.integrations_feature_flag.config.ts
- x-pack/test/security_solution_api_integration/test_suites/security_solution_endpoint/configs/integrations_feature_flag.config.ts

6 changes: 6 additions & 0 deletions x-pack/plugins/fleet/common/types/models/agent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ interface AgentBase {
components?: FleetServerAgentComponent[];
agent?: FleetServerAgentMetadata;
unhealthy_reason?: UnhealthyReason[];
namespaces?: string[];
}

export enum UnhealthyReason {
Expand Down Expand Up @@ -348,6 +349,11 @@ export interface FleetServerAgent {
* Unhealthy reason: input, output, other
*/
unhealthy_reason?: UnhealthyReason[];

/**
* Namespaces
*/
namespaces?: string[];
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,5 @@ export interface FleetServerEnrollmentAPIKey {
expire_at?: string;
created_at?: string;
updated_at?: string;
namespaces?: string[];
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,23 +9,57 @@ import type { ElasticsearchClient, SavedObjectsClientContract } from '@kbn/core/

import { getAgentsPerOutput } from './agents_per_output';

jest.mock('../services/agents', () => {
return {
getAgentsByKuery: jest
.fn()
.mockImplementation(async (esClient, soClient, { kuery }: { kuery: string }) => {
if (kuery.includes('policy_id:policy1')) {
return {
total: 0,
};
} else {
return {
total: 1,
};
}
}),
};
});

jest.mock('../services', () => {
return {
agentPolicyService: {
list: jest.fn().mockResolvedValue({
items: [
{ agents: 0, data_output_id: 'logstash1', monitoring_output_id: 'kafka1' },
{ agents: 1 },
{ agents: 1, data_output_id: 'logstash1' },
{ agents: 1, monitoring_output_id: 'kafka1' },
{ agents: 1, data_output_id: 'elasticsearch2', monitoring_output_id: 'elasticsearch2' },
{ agents: 1, data_output_id: 'elasticsearch3', monitoring_output_id: 'elasticsearch3' },
{ id: 'policy1', agents: 0, data_output_id: 'logstash1', monitoring_output_id: 'kafka1' },
{ id: 'policy2', agents: 1 },
{ id: 'policy3', agents: 1, data_output_id: 'logstash1' },
{ id: 'policy4', agents: 1, monitoring_output_id: 'kafka1' },
{
id: 'policy5',
agents: 1,
data_output_id: 'elasticsearch2',
monitoring_output_id: 'elasticsearch2',
},
{
id: 'policy5',
agents: 1,
data_output_id: 'elasticsearch3',
monitoring_output_id: 'elasticsearch3',
},
{
id: 'policy6',
agents: 1,
data_output_id: 'es-containerhost',
monitoring_output_id: 'es-containerhost',
},
{ agents: 1, data_output_id: 'remote-es', monitoring_output_id: 'remote-es' },
{
id: 'policy7',
agents: 1,
data_output_id: 'remote-es',
monitoring_output_id: 'remote-es',
},
],
}),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export async function getAgentsPerOutput(

const { items: agentPolicies } = await agentPolicyService.list(soClient, {
esClient,
withAgentCount: true,
Copy link
Contributor

@juliaElastic juliaElastic Jun 11, 2024

Choose a reason for hiding this comment

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

is the withAgentCount parameter still used elsewhere to call agentPolicyService.list?

Copy link
Member Author

Choose a reason for hiding this comment

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

Looks like it's used in observability_solution, I reverted that change, and use a scoped to space saved object client instead

page: 1,
perPage: SO_SEARCH_LIMIT,
withAgentCount: true,
});

const outputTypes: { [key: string]: AgentsPerOutputType } = {};
Expand Down
5 changes: 4 additions & 1 deletion x-pack/plugins/fleet/server/mocks/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ export const createFleetRequestHandlerContextMock = (): jest.Mocked<
asCurrentUser: createPackagePolicyServiceMock(),
asInternalUser: createPackagePolicyServiceMock(),
},
uninstallTokenService: {
asCurrentUser: createUninstallTokenServiceMock(),
},
internalSoClient: savedObjectsClientMock.create(),
spaceId: 'default',
limitedToPackages: undefined,
Expand Down Expand Up @@ -182,7 +185,6 @@ export const createPackagePolicyServiceMock = (): jest.Mocked<PackagePolicyClien
/**
* Create mock AgentPolicyService
*/

export const createMockAgentPolicyService = (): jest.Mocked<AgentPolicyServiceInterface> => {
return {
get: jest.fn(),
Expand Down Expand Up @@ -238,5 +240,6 @@ export function createUninstallTokenServiceMock(): UninstallTokenServiceInterfac
encryptTokens: jest.fn(),
checkTokenValidityForAllPolicies: jest.fn(),
checkTokenValidityForPolicy: jest.fn(),
scoped: jest.fn().mockImplementation(() => createUninstallTokenServiceMock()),
};
}
12 changes: 12 additions & 0 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,18 @@ export class FleetPlugin
asInternalUser: agentService.asInternalUser,
};
},
get uninstallTokenService() {
const uninstallTokenService = new UninstallTokenService(
appContextService.getEncryptedSavedObjectsStart()!.getClient({
includedHiddenTypes: [UNINSTALL_TOKENS_SAVED_OBJECT_TYPE],
}),
appContextService.getInternalUserSOClientForSpaceId(soClient.getCurrentNamespace())
);

return {
asCurrentUser: uninstallTokenService,
};
},
get packagePolicyService() {
const service = plugin.setupPackagePolicyService();

Expand Down
60 changes: 38 additions & 22 deletions x-pack/plugins/fleet/server/routes/agent/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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 @@ -20,6 +21,7 @@ import type {
GetAgentUploadsResponse,
PostAgentReassignResponse,
PostRetrieveAgentsByActionsResponse,
Agent,
} from '../../../common/types';
import type {
GetAgentsRequestSchema,
Expand All @@ -37,22 +39,36 @@ import type {
GetAgentUploadFileRequestSchema,
DeleteAgentUploadFileRequestSchema,
PostRetrieveAgentsByActionsRequestSchema,
FleetRequestHandler,
} from '../../types';
import { defaultFleetErrorHandler } from '../../errors';
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`);
}
}

export const getAgentHandler: RequestHandler<
export const getAgentHandler: FleetRequestHandler<
TypeOf<typeof GetOneAgentRequestSchema.params>,
TypeOf<typeof GetOneAgentRequestSchema.query>
> = async (context, request, response) => {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser;
const soClient = coreContext.savedObjects.client;

try {
let agent = await AgentService.getAgentById(esClient, soClient, request.params.agentId);
const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]);
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) {
agent = (await fetchAndAssignAgentMetrics(esClientCurrentUser, [agent]))[0];
Expand All @@ -77,10 +93,10 @@ export const getAgentHandler: RequestHandler<
export const deleteAgentHandler: RequestHandler<
TypeOf<typeof DeleteAgentRequestSchema.params>
> = async (context, request, response) => {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;

try {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;

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

const body = {
Expand Down Expand Up @@ -162,17 +178,16 @@ export const bulkUpdateAgentTagsHandler: RequestHandler<
}
};

export const getAgentsHandler: RequestHandler<
export const getAgentsHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetAgentsRequestSchema.query>
> = async (context, request, response) => {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]);
const { agentClient } = fleetContext;
const esClientCurrentUser = coreContext.elasticsearch.client.asCurrentUser;
const soClient = coreContext.savedObjects.client;

try {
const agentRes = await AgentService.getAgentsByKuery(esClient, soClient, {
const agentRes = await agentClient.asCurrentUser.listAgents({
page: request.query.page,
perPage: request.query.perPage,
showInactive: request.query.showInactive,
Expand Down Expand Up @@ -300,19 +315,20 @@ export const postBulkAgentReassignHandler: RequestHandler<
}
};

export const getAgentStatusForAgentPolicyHandler: RequestHandler<
export const getAgentStatusForAgentPolicyHandler: FleetRequestHandler<
undefined,
TypeOf<typeof GetAgentStatusRequestSchema.query>
> = async (context, request, response) => {
const coreContext = await context.core;
const esClient = coreContext.elasticsearch.client.asInternalUser;
const soClient = coreContext.savedObjects.client;
try {
const results = await AgentService.getAgentStatusForAgentPolicy(
const [coreContext, fleetContext] = await Promise.all([context.core, context.fleet]);
const esClient = coreContext.elasticsearch.client.asInternalUser;
const soClient = fleetContext.internalSoClient;
const results = await getAgentStatusForAgentPolicy(
esClient,
soClient,
request.query.policyId,
request.query.kuery
request.query.kuery,
coreContext.savedObjects.client.getCurrentNamespace()
);

const body: GetAgentStatusResponse = { results };
Expand Down
Loading