diff --git a/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts b/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts index 428378178afc8..6fcd0433e2e83 100644 --- a/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts +++ b/x-pack/plugins/apm/server/lib/fleet/register_fleet_policy_callbacks.ts @@ -7,8 +7,7 @@ import { APMPlugin, APMRouteHandlerResources } from '../..'; import { - ExternalCallback, - PostPackagePolicyDeleteCallback, + PostPackagePolicyCreateCallback, PutPackagePolicyUpdateCallback, } from '../../../../fleet/server'; import { @@ -60,7 +59,9 @@ export async function registerFleetPolicyCallbacks({ }); } -type ExternalCallbackParams = Parameters; +type ExternalCallbackParams = + | Parameters + | Parameters; export type PackagePolicy = NewPackagePolicy | UpdatePackagePolicy; type Context = ExternalCallbackParams[1]; type Request = ExternalCallbackParams[2]; @@ -81,7 +82,7 @@ function registerPackagePolicyExternalCallback({ logger: NonNullable; }) { const callbackFn: - | PostPackagePolicyDeleteCallback + | PostPackagePolicyCreateCallback | PutPackagePolicyUpdateCallback = async ( packagePolicy: PackagePolicy, context: Context, diff --git a/x-pack/plugins/fleet/common/mocks.ts b/x-pack/plugins/fleet/common/mocks.ts index 7ea4be0ee35c6..eb81ea2d6a0ac 100644 --- a/x-pack/plugins/fleet/common/mocks.ts +++ b/x-pack/plugins/fleet/common/mocks.ts @@ -5,7 +5,7 @@ * 2.0. */ -import type { NewPackagePolicy, PackagePolicy } from './types'; +import type { NewPackagePolicy, PackagePolicy, DeletePackagePoliciesResponse } from './types'; export const createNewPackagePolicyMock = (): NewPackagePolicy => { return { @@ -45,3 +45,14 @@ export const createPackagePolicyMock = (): PackagePolicy => { ], }; }; + +export const deletePackagePolicyMock = (): DeletePackagePoliciesResponse => { + const newPackagePolicy = createNewPackagePolicyMock(); + return [ + { + id: 'c6d16e42-c32d-4dce-8a88-113cfe276ad1', + success: true, + package: newPackagePolicy.package, + }, + ]; +}; diff --git a/x-pack/plugins/fleet/server/mocks/index.ts b/x-pack/plugins/fleet/server/mocks/index.ts index 9f07dfac9670b..43b455045e72b 100644 --- a/x-pack/plugins/fleet/server/mocks/index.ts +++ b/x-pack/plugins/fleet/server/mocks/index.ts @@ -62,7 +62,7 @@ export const xpackMocks = { createRequestHandlerContext: createCoreRequestHandlerContextMock, }; -export const createPackagePolicyServiceMock = () => { +export const createPackagePolicyServiceMock = (): jest.Mocked => { return { compilePackagePolicyInputs: jest.fn(), buildPackagePolicyFromPackage: jest.fn(), @@ -75,10 +75,11 @@ export const createPackagePolicyServiceMock = () => { listIds: jest.fn(), update: jest.fn(), runExternalCallbacks: jest.fn(), + runDeleteExternalCallbacks: jest.fn(), upgrade: jest.fn(), getUpgradeDryRunDiff: jest.fn(), getUpgradePackagePolicyInfo: jest.fn(), - } as jest.Mocked; + }; }; /** diff --git a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts index b9f0f6c69d4e7..77e7a2c4ede1a 100644 --- a/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts +++ b/x-pack/plugins/fleet/server/routes/package_policy/handlers.ts @@ -181,6 +181,7 @@ export const deletePackagePolicyHandler: RequestHandler< } catch (error) { const logger = appContextService.getLogger(); logger.error(`An error occurred executing external callback: ${error}`); + logger.error(error); } return response.ok({ body, diff --git a/x-pack/plugins/fleet/server/services/agent_policy.test.ts b/x-pack/plugins/fleet/server/services/agent_policy.test.ts index a020b95ca3302..3267b2b7e2665 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.test.ts @@ -12,6 +12,9 @@ import type { AgentPolicy, NewAgentPolicy, Output } from '../types'; import { agentPolicyService } from './agent_policy'; import { agentPolicyUpdateEventHandler } from './agent_policy_update'; +import { getAgentsByKuery } from './agents'; +import { packagePolicyService } from './package_policy'; + function getSavedObjectMock(agentPolicyAttributes: any) { const mock = savedObjectsClientMock.create(); mock.get.mockImplementation(async (type: string, id: string) => { @@ -63,6 +66,8 @@ jest.mock('./output', () => { }); jest.mock('./agent_policy_update'); +jest.mock('./agents'); +jest.mock('./package_policy'); function getAgentPolicyUpdateMock() { return (agentPolicyUpdateEventHandler as unknown) as jest.Mock< @@ -123,6 +128,36 @@ describe('agent policy', () => { }); }); + describe('delete', () => { + let soClient: ReturnType; + let esClient: ReturnType['asInternalUser']; + + beforeEach(() => { + soClient = getSavedObjectMock({ revision: 1, package_policies: ['package-1'] }); + esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + (getAgentsByKuery as jest.Mock).mockResolvedValue({ + agents: [], + total: 0, + page: 1, + perPage: 10, + }); + + (packagePolicyService.delete as jest.Mock).mockResolvedValue([ + { + id: 'package-1', + }, + ]); + }); + + it('should run package policy delete external callbacks', async () => { + await agentPolicyService.delete(soClient, esClient, 'mocked'); + expect(packagePolicyService.runDeleteExternalCallbacks).toHaveBeenCalledWith([ + { id: 'package-1' }, + ]); + }); + }); + describe('bumpRevision', () => { it('should call agentPolicyUpdateEventHandler with updated event once', async () => { const soClient = getSavedObjectMock({ diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index d3cccd4c07f3c..8539db05ffb54 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -45,6 +45,7 @@ import type { FleetServerPolicy, Installation, Output, + DeletePackagePoliciesResponse, } from '../../common'; import { AgentPolicyNameExistsError, HostedAgentPolicyRestrictionRelatedError } from '../errors'; import { @@ -616,7 +617,7 @@ class AgentPolicyService { } if (agentPolicy.package_policies && agentPolicy.package_policies.length) { - await packagePolicyService.delete( + const deletedPackagePolicies: DeletePackagePoliciesResponse = await packagePolicyService.delete( soClient, esClient, agentPolicy.package_policies as string[], @@ -624,6 +625,13 @@ class AgentPolicyService { skipUnassignFromAgentPolicies: true, } ); + try { + await packagePolicyService.runDeleteExternalCallbacks(deletedPackagePolicies); + } catch (error) { + const logger = appContextService.getLogger(); + logger.error(`An error occurred executing external callback: ${error}`); + logger.error(error); + } } if (agentPolicy.is_preconfigured) { diff --git a/x-pack/plugins/fleet/server/services/package_policy.test.ts b/x-pack/plugins/fleet/server/services/package_policy.test.ts index 66128c7e6c3e2..204650574e92a 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.test.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.test.ts @@ -20,6 +20,12 @@ import type { PutPackagePolicyUpdateCallback, PostPackagePolicyCreateCallback } import { createAppContextStartContractMock, xpackMocks } from '../mocks'; +import type { PostPackagePolicyDeleteCallback } from '../types'; + +import type { DeletePackagePoliciesResponse } from '../../common'; + +import { IngestManagerError } from '../errors'; + import { packagePolicyService } from './package_policy'; import { appContextService } from './app_context'; @@ -815,6 +821,78 @@ describe('Package policy service', () => { }); }); + describe('runDeleteExternalCallbacks', () => { + let callbackOne: jest.MockedFunction; + let callbackTwo: jest.MockedFunction; + let callingOrder: string[]; + let deletedPackagePolicies: DeletePackagePoliciesResponse; + + beforeEach(() => { + appContextService.start(createAppContextStartContractMock()); + callingOrder = []; + deletedPackagePolicies = [ + { id: 'a', success: true }, + { id: 'a', success: true }, + ]; + callbackOne = jest.fn(async (deletedPolicies) => { + callingOrder.push('one'); + }); + callbackTwo = jest.fn(async (deletedPolicies) => { + callingOrder.push('two'); + }); + appContextService.addExternalCallback('postPackagePolicyDelete', callbackOne); + appContextService.addExternalCallback('postPackagePolicyDelete', callbackTwo); + }); + + afterEach(() => { + appContextService.stop(); + }); + + it('should execute external callbacks', async () => { + await packagePolicyService.runDeleteExternalCallbacks(deletedPackagePolicies); + + expect(callbackOne).toHaveBeenCalledWith(deletedPackagePolicies); + expect(callbackTwo).toHaveBeenCalledWith(deletedPackagePolicies); + expect(callingOrder).toEqual(['one', 'two']); + }); + + it("should execute all external callbacks even if one throw's", async () => { + callbackOne.mockImplementation(async (deletedPolicies) => { + callingOrder.push('one'); + throw new Error('foo'); + }); + await expect( + packagePolicyService.runDeleteExternalCallbacks(deletedPackagePolicies) + ).rejects.toThrow(IngestManagerError); + expect(callingOrder).toEqual(['one', 'two']); + }); + + it('should provide an array of errors encountered by running external callbacks', async () => { + let error: IngestManagerError; + const callbackOneError = new Error('foo 1'); + const callbackTwoError = new Error('foo 2'); + + callbackOne.mockImplementation(async (deletedPolicies) => { + callingOrder.push('one'); + throw callbackOneError; + }); + callbackTwo.mockImplementation(async (deletedPolicies) => { + callingOrder.push('two'); + throw callbackTwoError; + }); + + await packagePolicyService.runDeleteExternalCallbacks(deletedPackagePolicies).catch((e) => { + error = e; + }); + + expect(error!.message).toEqual( + '2 encountered while executing package delete external callbacks' + ); + expect(error!.meta).toEqual([callbackOneError, callbackTwoError]); + expect(callingOrder).toEqual(['one', 'two']); + }); + }); + describe('runExternalCallbacks', () => { let context: ReturnType; let request: KibanaRequest; diff --git a/x-pack/plugins/fleet/server/services/package_policy.ts b/x-pack/plugins/fleet/server/services/package_policy.ts index 561cbef952f8d..61152b07f793b 100644 --- a/x-pack/plugins/fleet/server/services/package_policy.ts +++ b/x-pack/plugins/fleet/server/services/package_policy.ts @@ -428,9 +428,9 @@ class PackagePolicyService { name: packagePolicy.name, success: true, package: { - name: packagePolicy.name, - title: '', - version: packagePolicy.version || '', + name: packagePolicy.package?.name || '', + title: packagePolicy.package?.title || '', + version: packagePolicy.package?.version || '', }, }); } catch (error) { @@ -642,7 +642,9 @@ class PackagePolicyService { public async runExternalCallbacks( externalCallbackType: A, - packagePolicy: NewPackagePolicy | DeletePackagePoliciesResponse, + packagePolicy: A extends 'postPackagePolicyDelete' + ? DeletePackagePoliciesResponse + : NewPackagePolicy, context: RequestHandlerContext, request: KibanaRequest ): Promise; @@ -653,14 +655,7 @@ class PackagePolicyService { request: KibanaRequest ): Promise { if (externalCallbackType === 'postPackagePolicyDelete') { - const externalCallbacks = appContextService.getExternalCallbacks(externalCallbackType); - if (externalCallbacks && externalCallbacks.size > 0) { - for (const callback of externalCallbacks) { - if (Array.isArray(packagePolicy)) { - await callback(packagePolicy, context, request); - } - } - } + return await this.runDeleteExternalCallbacks(packagePolicy as DeletePackagePoliciesResponse); } else { if (!Array.isArray(packagePolicy)) { let newData = packagePolicy; @@ -682,6 +677,32 @@ class PackagePolicyService { } } } + + public async runDeleteExternalCallbacks( + deletedPackagePolicies: DeletePackagePoliciesResponse + ): Promise { + const externalCallbacks = appContextService.getExternalCallbacks('postPackagePolicyDelete'); + const errorsThrown: Error[] = []; + + if (externalCallbacks && externalCallbacks.size > 0) { + for (const callback of externalCallbacks) { + // Failures from an external callback should not prevent other external callbacks from being + // executed. Errors (if any) will be collected and `throw`n after processing the entire set + try { + await callback(deletedPackagePolicies); + } catch (error) { + errorsThrown.push(error); + } + } + + if (errorsThrown.length > 0) { + throw new IngestManagerError( + `${errorsThrown.length} encountered while executing package delete external callbacks`, + errorsThrown + ); + } + } + } } function assignStreamIdToInput(packagePolicyId: string, input: NewPackagePolicyInput) { diff --git a/x-pack/plugins/fleet/server/types/extensions.ts b/x-pack/plugins/fleet/server/types/extensions.ts index bca9cc016f828..a7f4a422cc2ae 100644 --- a/x-pack/plugins/fleet/server/types/extensions.ts +++ b/x-pack/plugins/fleet/server/types/extensions.ts @@ -7,6 +7,8 @@ import type { KibanaRequest, RequestHandlerContext } from 'kibana/server'; +import type { DeepReadonly } from 'utility-types'; + import type { DeletePackagePoliciesResponse, NewPackagePolicy, @@ -14,9 +16,7 @@ import type { } from '../../common'; export type PostPackagePolicyDeleteCallback = ( - deletedPackagePolicies: DeletePackagePoliciesResponse, - context: RequestHandlerContext, - request: KibanaRequest + deletedPackagePolicies: DeepReadonly ) => Promise; export type PostPackagePolicyCreateCallback = ( diff --git a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts index 1a2e1a7c4ee6f..5a47c8a616c00 100644 --- a/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts +++ b/x-pack/plugins/security_solution/server/endpoint/endpoint_app_context_services.ts @@ -22,6 +22,7 @@ import { PluginStartContract as AlertsPluginStartContract } from '../../../alert import { getPackagePolicyCreateCallback, getPackagePolicyUpdateCallback, + getPackagePolicyDeleteCallback, } from '../fleet_integration/fleet_integration'; import { ManifestManager } from './services/artifacts'; import { AppClientFactory } from '../client'; @@ -102,6 +103,11 @@ export class EndpointAppContextService { 'packagePolicyUpdate', getPackagePolicyUpdateCallback(dependencies.logger, dependencies.licenseService) ); + + dependencies.registerIngestCallback( + 'postPackagePolicyDelete', + getPackagePolicyDeleteCallback(dependencies.exceptionListsClient, this.experimentalFeatures) + ); } } diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts index 56c462de54c52..d0bbb3b346ea8 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.test.ts @@ -6,7 +6,7 @@ */ import { httpServerMock, loggingSystemMock } from 'src/core/server/mocks'; -import { createNewPackagePolicyMock } from '../../../fleet/common/mocks'; +import { createNewPackagePolicyMock, deletePackagePolicyMock } from '../../../fleet/common/mocks'; import { policyFactory, policyFactoryWithoutPaidFeatures, @@ -14,6 +14,7 @@ import { import { buildManifestManagerMock } from '../endpoint/services/artifacts/manifest_manager/manifest_manager.mock'; import { getPackagePolicyCreateCallback, + getPackagePolicyDeleteCallback, getPackagePolicyUpdateCallback, } from './fleet_integration'; import { KibanaRequest } from 'kibana/server'; @@ -28,6 +29,7 @@ import { EndpointDocGenerator } from '../../common/endpoint/generate_data'; import { ProtectionModes } from '../../common/endpoint/types'; import type { SecuritySolutionRequestHandlerContext } from '../types'; import { getExceptionListClientMock } from '../../../lists/server/services/exception_lists/exception_list_client.mock'; +import { getExceptionListSchemaMock } from '../../../lists/common/schemas/response/exception_list_schema.mock'; import { ExceptionListClient } from '../../../lists/server'; import { InternalArtifactCompleteSchema } from '../endpoint/schemas/artifacts'; import { ManifestManager } from '../endpoint/services/artifacts/manifest_manager'; @@ -35,6 +37,12 @@ import { getMockArtifacts, toArtifactRecords } from '../endpoint/lib/artifacts/m import { Manifest } from '../endpoint/lib/artifacts'; import { NewPackagePolicy } from '../../../fleet/common/types/models'; import { ManifestSchema } from '../../common/endpoint/schema/manifest'; +import { + allowedExperimentalValues, + ExperimentalFeatures, +} from '../../common/experimental_features'; +import { DeletePackagePoliciesResponse } from '../../../fleet/common'; +import { ExceptionListSchema } from '@kbn/securitysolution-io-ts-list-types'; describe('ingest_integration tests ', () => { let endpointAppContextMock: EndpointAppContextServiceStartContract; @@ -282,4 +290,66 @@ describe('ingest_integration tests ', () => { expect(updatedPolicyConfig.inputs[0]!.config!.policy.value).toEqual(mockPolicy); }); }); + + describe('package policy delete callback with trusted apps by policy enabled', () => { + const invokeDeleteCallback = async ( + experimentalFeatures?: ExperimentalFeatures + ): Promise => { + const callback = getPackagePolicyDeleteCallback(exceptionListClient, experimentalFeatures); + await callback(deletePackagePolicyMock()); + }; + + let removedPolicies: DeletePackagePoliciesResponse; + let policyId: string; + let fakeTA: ExceptionListSchema; + + beforeEach(() => { + removedPolicies = deletePackagePolicyMock(); + policyId = removedPolicies[0].id; + fakeTA = { + ...getExceptionListSchemaMock(), + tags: [`policy:${policyId}`], + }; + + exceptionListClient.findExceptionListItem = jest + .fn() + .mockResolvedValueOnce({ data: [fakeTA], total: 1 }); + exceptionListClient.updateExceptionListItem = jest + .fn() + .mockResolvedValueOnce({ ...fakeTA, tags: [] }); + }); + + it('removes policy from trusted app FF enabled', async () => { + await invokeDeleteCallback({ + ...allowedExperimentalValues, + trustedAppsByPolicyEnabled: true, // Needs to be enabled, it needs also a test with this disabled. + }); + + expect(exceptionListClient.findExceptionListItem).toHaveBeenCalledWith({ + filter: `exception-list-agnostic.attributes.tags:"policy:${policyId}"`, + listId: 'endpoint_trusted_apps', + namespaceType: 'agnostic', + page: 1, + perPage: 50, + sortField: undefined, + sortOrder: undefined, + }); + + expect(exceptionListClient.updateExceptionListItem).toHaveBeenCalledWith({ + ...fakeTA, + namespaceType: fakeTA.namespace_type, + osTypes: fakeTA.os_types, + tags: [], + }); + }); + + it("doesn't remove policy from trusted app FF disabled", async () => { + await invokeDeleteCallback({ + ...allowedExperimentalValues, + }); + + expect(exceptionListClient.findExceptionListItem).toHaveBeenCalledTimes(0); + expect(exceptionListClient.updateExceptionListItem).toHaveBeenCalledTimes(0); + }); + }); }); diff --git a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts index 62c7b3719d6a6..09810a6c88c3d 100644 --- a/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts +++ b/x-pack/plugins/security_solution/server/fleet_integration/fleet_integration.ts @@ -11,9 +11,12 @@ import { PluginStartContract as AlertsStartContract } from '../../../alerting/se import { SecurityPluginStart } from '../../../security/server'; import { PostPackagePolicyCreateCallback, + PostPackagePolicyDeleteCallback, PutPackagePolicyUpdateCallback, } from '../../../fleet/server'; + import { NewPackagePolicy, UpdatePackagePolicy } from '../../../fleet/common'; + import { NewPolicyData, PolicyConfig } from '../../common/endpoint/types'; import { ManifestManager } from '../endpoint/services'; import { AppClientFactory } from '../client'; @@ -22,6 +25,8 @@ import { installPrepackagedRules } from './handlers/install_prepackaged_rules'; import { createPolicyArtifactManifest } from './handlers/create_policy_artifact_manifest'; import { createDefaultPolicy } from './handlers/create_default_policy'; import { validatePolicyAgainstLicense } from './handlers/validate_policy_against_license'; +import { removePolicyFromTrustedApps } from './handlers/remove_policy_from_trusted_apps'; +import { ExperimentalFeatures } from '../../common/experimental_features'; const isEndpointPackagePolicy = ( packagePolicy: T @@ -126,3 +131,21 @@ export const getPackagePolicyUpdateCallback = ( return newPackagePolicy; }; }; + +export const getPackagePolicyDeleteCallback = ( + exceptionsClient: ExceptionListClient | undefined, + experimentalFeatures: ExperimentalFeatures | undefined +): PostPackagePolicyDeleteCallback => { + return async (deletePackagePolicy): Promise => { + if (!exceptionsClient) { + return; + } + const policiesToRemove: Array> = []; + for (const policy of deletePackagePolicy) { + if (isEndpointPackagePolicy(policy) && experimentalFeatures?.trustedAppsByPolicyEnabled) { + policiesToRemove.push(removePolicyFromTrustedApps(exceptionsClient, policy)); + } + } + await Promise.all(policiesToRemove); + }; +}; diff --git a/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_policy_from_trusted_apps.ts b/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_policy_from_trusted_apps.ts new file mode 100644 index 0000000000000..88af71508f33a --- /dev/null +++ b/x-pack/plugins/security_solution/server/fleet_integration/handlers/remove_policy_from_trusted_apps.ts @@ -0,0 +1,61 @@ +/* + * 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 { ENDPOINT_TRUSTED_APPS_LIST_ID } from '@kbn/securitysolution-list-constants'; +import { ExceptionListClient } from '../../../../lists/server'; +import { PostPackagePolicyDeleteCallback } from '../../../../fleet/server'; + +/** + * Removes policy from trusted apps + */ +export const removePolicyFromTrustedApps = async ( + exceptionsClient: ExceptionListClient, + policy: Parameters[0][0] +) => { + let page = 1; + + const findTrustedAppsByPolicy = async (currentPage: number) => { + return exceptionsClient.findExceptionListItem({ + listId: ENDPOINT_TRUSTED_APPS_LIST_ID, + filter: `exception-list-agnostic.attributes.tags:"policy:${policy.id}"`, + namespaceType: 'agnostic', + page: currentPage, + perPage: 50, + sortField: undefined, + sortOrder: undefined, + }); + }; + + let findResponse = await findTrustedAppsByPolicy(page); + if (!findResponse) { + return; + } + const trustedApps = findResponse.data; + + while (findResponse && (trustedApps.length < findResponse.total || findResponse.data.length)) { + page += 1; + findResponse = await findTrustedAppsByPolicy(page); + if (findResponse) { + trustedApps.push(...findResponse.data); + } + } + + const updates = []; + for (const trustedApp of trustedApps) { + updates.push( + exceptionsClient.updateExceptionListItem({ + ...trustedApp, + itemId: trustedApp.item_id, + namespaceType: trustedApp.namespace_type, + osTypes: trustedApp.os_types, + tags: trustedApp.tags.filter((currentPolicy) => currentPolicy !== `policy:${policy.id}`), + }) + ); + } + + await Promise.all(updates); +};