Skip to content

Commit

Permalink
[Fleet] Fix side effects accross spaces (elastic#192472)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet authored Sep 12, 2024
1 parent 4120cdd commit c492036
Show file tree
Hide file tree
Showing 12 changed files with 397 additions and 91 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import type { DownloadSourceBase, DownloadSource } from '../models';
import type { ListResult } from './common';

export interface GetOneDownloadSourceResponse {
item: DownloadSourceBase;
item: DownloadSource;
}

export interface DeleteDownloadSourceResponse {
Expand Down
17 changes: 10 additions & 7 deletions x-pack/plugins/fleet/server/services/agent_policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -952,8 +952,11 @@ describe('Agent policy', () => {
});
}
});

await agentPolicyService.removeOutputFromAll(soClient, esClient, 'output-id-123');
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValue(
soClient
);
mockedAppContextService.getInternalUserSOClientForSpaceId.mockReturnValue(soClient);
await agentPolicyService.removeOutputFromAll(esClient, 'output-id-123');

expect(mockedAgentPolicyServiceUpdate).toHaveBeenCalledTimes(2);
expect(mockedAgentPolicyServiceUpdate).toHaveBeenCalledWith(
Expand Down Expand Up @@ -993,6 +996,10 @@ describe('Agent policy', () => {
mockedDownloadSourceService.getDefaultDownloadSourceId.mockResolvedValue(
'default-download-source-id'
);
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValue(
soClient
);
mockedAppContextService.getInternalUserSOClientForSpaceId.mockReturnValue(soClient);
soClient.find.mockResolvedValue({
saved_objects: [
{
Expand All @@ -1010,11 +1017,7 @@ describe('Agent policy', () => {
],
} as any);

await agentPolicyService.removeDefaultSourceFromAll(
soClient,
esClient,
'default-download-source-id'
);
await agentPolicyService.removeDefaultSourceFromAll(esClient, 'default-download-source-id');

expect(mockedAgentPolicyServiceUpdate).toHaveBeenCalledTimes(2);
expect(mockedAgentPolicyServiceUpdate).toHaveBeenCalledWith(
Expand Down
121 changes: 71 additions & 50 deletions x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import type {
SavedObjectsClientContract,
SavedObject,
SavedObjectsUpdateResponse,
SavedObjectsFindOptions,
} from '@kbn/core/server';
import { SavedObjectsUtils } from '@kbn/core/server';

Expand Down Expand Up @@ -538,6 +539,7 @@ class AgentPolicyService {
fields?: string[];
esClient?: ElasticsearchClient;
withAgentCount?: boolean;
spaceId?: string;
}
): Promise<{
items: AgentPolicy[];
Expand All @@ -555,16 +557,22 @@ class AgentPolicyService {
kuery,
withPackagePolicies = false,
fields,
spaceId,
} = options;

const baseFindParams = {
const baseFindParams: SavedObjectsFindOptions = {
type: savedObjectType,
sortField,
sortOrder,
page,
perPage,
...(fields ? { fields } : {}),
};

if (spaceId) {
baseFindParams.namespaces = [spaceId];
}

const filter = kuery ? normalizeKuery(savedObjectType, kuery) : undefined;
let agentPoliciesSO;
try {
Expand Down Expand Up @@ -839,24 +847,22 @@ class AgentPolicyService {

/**
* Remove an output from all agent policies that are using it, and replace the output by the default ones.
* @param soClient
* @param esClient
* @param outputId
*/
public async removeOutputFromAll(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
outputId: string
) {
public async removeOutputFromAll(esClient: ElasticsearchClient, outputId: string) {
const savedObjectType = await getAgentPolicySavedObjectType();
const agentPolicies = (
await soClient.find<AgentPolicySOAttributes>({
type: savedObjectType,
fields: ['revision', 'data_output_id', 'monitoring_output_id'],
searchFields: ['data_output_id', 'monitoring_output_id'],
search: escapeSearchQueryPhrase(outputId),
perPage: SO_SEARCH_LIMIT,
})
await appContextService
.getInternalUserSOClientWithoutSpaceExtension()
.find<AgentPolicySOAttributes>({
type: savedObjectType,
fields: ['revision', 'data_output_id', 'monitoring_output_id'],
searchFields: ['data_output_id', 'monitoring_output_id'],
search: escapeSearchQueryPhrase(outputId),
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
})
).saved_objects.map(mapAgentPolicySavedObjectToAgentPolicy);

if (agentPolicies.length > 0) {
Expand All @@ -869,6 +875,9 @@ class AgentPolicyService {
await pMap(
agentPolicies,
async (agentPolicy) => {
const soClient = appContextService.getInternalUserSOClientForSpaceId(
agentPolicy.space_ids?.[0]
);
const existingAgentPolicy = await this.get(soClient, agentPolicy.id, true);

if (!existingAgentPolicy) {
Expand All @@ -888,10 +897,14 @@ class AgentPolicyService {
);
await pMap(
agentPolicies,
(agentPolicy) =>
this.update(soClient, esClient, agentPolicy.id, getAgentPolicy(agentPolicy), {
(agentPolicy) => {
const soClient = appContextService.getInternalUserSOClientForSpaceId(
agentPolicy.space_ids?.[0]
);
return this.update(soClient, esClient, agentPolicy.id, getAgentPolicy(agentPolicy), {
skipValidation: true,
}),
});
},
{
concurrency: 50,
}
Expand All @@ -903,28 +916,35 @@ class AgentPolicyService {
* Remove a Fleet Server from all agent policies that are using it, to use the default one instead.
*/
public async removeFleetServerHostFromAll(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
fleetServerHostId: string
) {
const savedObjectType = await getAgentPolicySavedObjectType();
const agentPolicies = (
await soClient.find<AgentPolicySOAttributes>({
type: savedObjectType,
fields: ['revision', 'fleet_server_host_id'],
searchFields: ['fleet_server_host_id'],
search: escapeSearchQueryPhrase(fleetServerHostId),
perPage: SO_SEARCH_LIMIT,
})
await appContextService
.getInternalUserSOClientWithoutSpaceExtension()
.find<AgentPolicySOAttributes>({
type: savedObjectType,
fields: ['revision', 'fleet_server_host_id'],
searchFields: ['fleet_server_host_id'],
search: escapeSearchQueryPhrase(fleetServerHostId),
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
})
).saved_objects.map(mapAgentPolicySavedObjectToAgentPolicy);

if (agentPolicies.length > 0) {
await pMap(
agentPolicies,
(agentPolicy) =>
this.update(soClient, esClient, agentPolicy.id, {
fleet_server_host_id: null,
}),
this.update(
appContextService.getInternalUserSOClientForSpaceId(agentPolicy.space_ids?.[0]),
esClient,
agentPolicy.id,
{
fleet_server_host_id: null,
}
),
{
concurrency: 50,
}
Expand Down Expand Up @@ -1445,35 +1465,36 @@ class AgentPolicyService {
* @param esClient
* @param downloadSourceId
*/
public async removeDefaultSourceFromAll(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
downloadSourceId: string
) {
public async removeDefaultSourceFromAll(esClient: ElasticsearchClient, downloadSourceId: string) {
const savedObjectType = await getAgentPolicySavedObjectType();
const agentPolicies = (
await soClient.find<AgentPolicySOAttributes>({
type: savedObjectType,
fields: ['revision', 'download_source_id'],
searchFields: ['download_source_id'],
search: escapeSearchQueryPhrase(downloadSourceId),
perPage: SO_SEARCH_LIMIT,
})
).saved_objects.map((so) => ({
id: so.id,
...so.attributes,
}));
await appContextService
.getInternalUserSOClientWithoutSpaceExtension()
.find<AgentPolicySOAttributes>({
type: savedObjectType,
fields: ['revision', 'download_source_id'],
searchFields: ['download_source_id'],
search: escapeSearchQueryPhrase(downloadSourceId),
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
})
).saved_objects.map(mapAgentPolicySavedObjectToAgentPolicy);

if (agentPolicies.length > 0) {
await pMap(
agentPolicies,
(agentPolicy) =>
this.update(soClient, esClient, agentPolicy.id, {
download_source_id:
agentPolicy.download_source_id === downloadSourceId
? null
: agentPolicy.download_source_id,
}),
this.update(
appContextService.getInternalUserSOClientForSpaceId(agentPolicy.space_ids?.[0]),
esClient,
agentPolicy.id,
{
download_source_id:
agentPolicy.download_source_id === downloadSourceId
? null
: agentPolicy.download_source_id,
}
),
{
concurrency: 50,
}
Expand Down
1 change: 0 additions & 1 deletion x-pack/plugins/fleet/server/services/download_source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ class DownloadSourceService {
throw new DownloadSourceError(`Default Download source ${id} cannot be deleted.`);
}
await agentPolicyService.removeDefaultSourceFromAll(
soClient,
appContextService.getInternalUserESClient(),
id
);
Expand Down
2 changes: 1 addition & 1 deletion x-pack/plugins/fleet/server/services/fleet_server_host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ export async function deleteFleetServerHost(
);
}

await agentPolicyService.removeFleetServerHostFromAll(soClient, esClient, id);
await agentPolicyService.removeFleetServerHostFromAll(esClient, id);

return await soClient.delete(FLEET_SERVER_HOST_SAVED_OBJECT_TYPE, id);
}
Expand Down
18 changes: 5 additions & 13 deletions x-pack/plugins/fleet/server/services/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ async function getAgentPoliciesPerOutput(outputId?: string, isDefault?: boolean)
const directAgentPolicies = await agentPolicyService.list(internalSoClientWithoutSpaceExtension, {
kuery: agentPoliciesKuery,
perPage: SO_SEARCH_LIMIT,
spaceId: '*',
});
const directAgentPolicyIds = directAgentPolicies?.items.map((policy) => policy.id);

Expand All @@ -162,6 +163,7 @@ async function getAgentPoliciesPerOutput(outputId?: string, isDefault?: boolean)
const packagePolicySOs = await packagePolicyService.list(internalSoClientWithoutSpaceExtension, {
kuery: packagePoliciesKuery,
perPage: SO_SEARCH_LIMIT,
spaceId: '*',
});
const agentPolicyIdsFromPackagePolicies = [
...new Set(
Expand Down Expand Up @@ -234,6 +236,7 @@ async function findPoliciesWithFleetServerOrSynthetics(outputId?: string, isDefa
internalSoClientWithoutSpaceExtension,
{
fields: ['policy_ids', 'package.name'],
spaceId: '*',
kuery: [FLEET_APM_PACKAGE, FLEET_SYNTHETICS_PACKAGE, FLEET_SERVER_PACKAGE]
.map((packageName) => `${PACKAGE_POLICY_SAVED_OBJECT_TYPE}.package.name:${packageName}`)
.join(' or '),
Expand Down Expand Up @@ -820,20 +823,9 @@ class OutputService {
throw new OutputUnauthorizedError(`Default monitoring output ${id} cannot be deleted.`);
}

const internalSoClientWithoutSpaceExtension =
appContextService.getInternalUserSOClientWithoutSpaceExtension();
await packagePolicyService.removeOutputFromAll(appContextService.getInternalUserESClient(), id);

await packagePolicyService.removeOutputFromAll(
internalSoClientWithoutSpaceExtension,
appContextService.getInternalUserESClient(),
id
);

await agentPolicyService.removeOutputFromAll(
internalSoClientWithoutSpaceExtension,
appContextService.getInternalUserESClient(),
id
);
await agentPolicyService.removeOutputFromAll(appContextService.getInternalUserESClient(), id);

auditLoggingService.writeCustomSoAuditLog({
action: 'delete',
Expand Down
8 changes: 7 additions & 1 deletion x-pack/plugins/fleet/server/services/package_policy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5157,8 +5157,14 @@ describe('Package policy service', () => {
});
}
});
appContextService.start(
createAppContextStartContractMock(undefined, false, {
internal: soClient,
withoutSpaceExtensions: soClient,
})
);

await packagePolicyService.removeOutputFromAll(soClient, esClient, 'output-id-123');
await packagePolicyService.removeOutputFromAll(esClient, 'output-id-123');

expect(updateSpy).toHaveBeenCalledTimes(1);
expect(updateSpy).toHaveBeenCalledWith(
Expand Down
29 changes: 17 additions & 12 deletions x-pack/plugins/fleet/server/services/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2030,20 +2030,19 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
}

public async removeOutputFromAll(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
outputId: string
) {
public async removeOutputFromAll(esClient: ElasticsearchClient, outputId: string) {
const savedObjectType = await getPackagePolicySavedObjectType();
const packagePolicies = (
await soClient.find<PackagePolicySOAttributes>({
type: savedObjectType,
fields: ['name', 'enabled', 'policy_ids', 'inputs', 'output_id'],
searchFields: ['output_id'],
search: escapeSearchQueryPhrase(outputId),
perPage: SO_SEARCH_LIMIT,
})
await appContextService
.getInternalUserSOClientWithoutSpaceExtension()
.find<PackagePolicySOAttributes>({
type: savedObjectType,
fields: ['name', 'enabled', 'policy_ids', 'inputs', 'output_id'],
searchFields: ['output_id'],
search: escapeSearchQueryPhrase(outputId),
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
})
).saved_objects.map(mapPackagePolicySavedObjectToPackagePolicy);

if (packagePolicies.length > 0) {
Expand All @@ -2060,6 +2059,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
await pMap(
packagePolicies,
async (packagePolicy) => {
const soClient = appContextService.getInternalUserSOClientForSpaceId(
packagePolicy.spaceIds?.[0]
);
const existingPackagePolicy = await this.get(soClient, packagePolicy.id);

if (!existingPackagePolicy) {
Expand Down Expand Up @@ -2087,6 +2089,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
await pMap(
packagePolicies,
(packagePolicy) => {
const soClient = appContextService.getInternalUserSOClientForSpaceId(
packagePolicy.spaceIds?.[0]
);
return this.update(
soClient,
esClient,
Expand Down
Loading

0 comments on commit c492036

Please sign in to comment.