[8.x] [Fleet] Reuse shared integration policies when duplicating agent policies (#217872) (#218002)

# Backport

This will backport the following commits from `main` to `8.x`:
- [[Fleet] Reuse shared integration policies when duplicating agent
policies (#217872)](https://github.com/elastic/kibana/pull/217872)

<!--- Backport version: 9.6.6 -->

### Questions ?
Please refer to the [Backport tool
documentation](https://github.com/sorenlouv/backport)

<!--BACKPORT [{"author":{"name":"Jill
Guyonnet","email":"jill.guyonnet@elastic.co"},"sourceCommit":{"committedDate":"2025-04-11T15:16:17Z","message":"[Fleet]
Reuse shared integration policies when duplicating agent policies
(#217872)\n\n## Summary\n\nCloses
https://github.com/elastic/kibana/issues/215335\n\nCurrently, when an
agent policy is duplicated, shared integration\npolicies are also
duplicated. This PR adds logic where the duplicated\nagent policy also
shares these integration policies.\n\n### Testing\n\n* Run ES with an
[Entreprise\nlicense](https://www.elastic.co/subscriptions) to avail of
reusable\nintegration policies.\n* Create an agent policy with a shared
integration policy and a\nnon-shared integration policy.\n* Duplicate
the agent policy: the duplicated policy should only\nduplicate the
non-shared integration policy and the shared integration\npolicy should
be reused.\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] The PR
description includes the appropriate Release Notes section,\nand the
correct `release_note:*` label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n###
Identify risks\n\nIncorrect package policies in duplicated agent
policies.","sha":"5c78ff18484e77b5ec5a4ba2ab341ed65db4f21c","branchLabelMapping":{"^v9.1.0$":"main","^v8.19.0$":"8.x","^v(\\d+).(\\d+).\\d+$":"$1.$2"}},"sourcePullRequest":{"labels":["release_note:enhancement","Team:Fleet","v9.0.0","backport:version","v9.1.0","v8.19.0"],"title":"[Fleet]
Reuse shared integration policies when duplicating agent
policies","number":217872,"url":"https://github.com/elastic/kibana/pull/217872","mergeCommit":{"message":"[Fleet]
Reuse shared integration policies when duplicating agent policies
(#217872)\n\n## Summary\n\nCloses
https://github.com/elastic/kibana/issues/215335\n\nCurrently, when an
agent policy is duplicated, shared integration\npolicies are also
duplicated. This PR adds logic where the duplicated\nagent policy also
shares these integration policies.\n\n### Testing\n\n* Run ES with an
[Entreprise\nlicense](https://www.elastic.co/subscriptions) to avail of
reusable\nintegration policies.\n* Create an agent policy with a shared
integration policy and a\nnon-shared integration policy.\n* Duplicate
the agent policy: the duplicated policy should only\nduplicate the
non-shared integration policy and the shared integration\npolicy should
be reused.\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] The PR
description includes the appropriate Release Notes section,\nand the
correct `release_note:*` label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n###
Identify risks\n\nIncorrect package policies in duplicated agent
policies.","sha":"5c78ff18484e77b5ec5a4ba2ab341ed65db4f21c"}},"sourceBranch":"main","suggestedTargetBranches":["9.0","8.x"],"targetPullRequestStates":[{"branch":"9.0","label":"v9.0.0","branchLabelMappingKey":"^v(\\d+).(\\d+).\\d+$","isSourceBranch":false,"state":"NOT_CREATED"},{"branch":"main","label":"v9.1.0","branchLabelMappingKey":"^v9.1.0$","isSourceBranch":true,"state":"MERGED","url":"https://github.com/elastic/kibana/pull/217872","number":217872,"mergeCommit":{"message":"[Fleet]
Reuse shared integration policies when duplicating agent policies
(#217872)\n\n## Summary\n\nCloses
https://github.com/elastic/kibana/issues/215335\n\nCurrently, when an
agent policy is duplicated, shared integration\npolicies are also
duplicated. This PR adds logic where the duplicated\nagent policy also
shares these integration policies.\n\n### Testing\n\n* Run ES with an
[Entreprise\nlicense](https://www.elastic.co/subscriptions) to avail of
reusable\nintegration policies.\n* Create an agent policy with a shared
integration policy and a\nnon-shared integration policy.\n* Duplicate
the agent policy: the duplicated policy should only\nduplicate the
non-shared integration policy and the shared integration\npolicy should
be reused.\n\n### Checklist\n\n- [x] [Unit or
functional\ntests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)\nwere
updated or added to match the most common scenarios\n- [ ] The PR
description includes the appropriate Release Notes section,\nand the
correct `release_note:*` label is applied per
the\n[guidelines](https://www.elastic.co/guide/en/kibana/master/contributing.html#kibana-release-notes-process)\n\n###
Identify risks\n\nIncorrect package policies in duplicated agent
policies.","sha":"5c78ff18484e77b5ec5a4ba2ab341ed65db4f21c"}},{"branch":"8.x","label":"v8.19.0","branchLabelMappingKey":"^v8.19.0$","isSourceBranch":false,"state":"NOT_CREATED"}]}]
BACKPORT-->

Co-authored-by: Jill Guyonnet <jill.guyonnet@elastic.co>
This commit is contained in:
Kibana Machine 2025-04-11 19:51:59 +02:00 committed by GitHub
parent 92623b406a
commit df83efc556
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 130 additions and 22 deletions

View file

@ -1337,6 +1337,98 @@ describe('Agent policy', () => {
);
}
});
it('should link shared package policies', async () => {
agentPolicyService.requireUniqueName = async () => {};
soClient = savedObjectsClientMock.create();
const mockPolicy = {
type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
references: [],
attributes: { revision: 1, package_policies: ['package-1'] } as any,
};
soClient.get.mockImplementation(async (type: string, id: string) => {
return {
id,
...mockPolicy,
};
});
soClient.find
.mockImplementationOnce(async () => ({
saved_objects: [
{
id: 'agent-policy-id',
score: 1,
...{ ...mockPolicy, name: 'mocked-policy' },
},
],
total: 1,
page: 1,
per_page: 1,
}))
.mockImplementationOnce(async () => ({
saved_objects: [
{
id: 'agent-policy-id-copy',
score: 1,
...{ ...mockPolicy, name: 'mocked-policy' },
},
],
total: 1,
page: 1,
per_page: 1,
}));
soClient.create.mockImplementation(async (type, attributes) => {
return {
attributes: attributes as unknown as NewAgentPolicy,
id: 'mocked',
type: 'mocked',
references: [],
};
});
const packagePolicies = [
{
id: 'package-1',
name: 'package-1',
policy_id: 'policy_1',
policy_ids: ['policy_1', 'policy_2'],
},
{
id: 'package-2',
name: 'package-2',
policy_id: 'policy_1',
policy_ids: ['policy_1'],
},
] as any;
mockedPackagePolicyService.findAllForAgentPolicy.mockReturnValue(packagePolicies);
mockedPackagePolicyService.list.mockResolvedValue({ items: packagePolicies } as any);
await agentPolicyService.copy(soClient, esClient, 'mocked', {
name: 'copy mocked',
});
expect(mockedPackagePolicyService.bulkCreate).toBeCalledWith(
expect.anything(),
expect.anything(),
[
{
name: 'package-2 (copy)',
policy_id: 'policy_1',
policy_ids: ['mocked'],
},
],
expect.anything()
);
expect(mockedPackagePolicyService.bulkUpdate).toBeCalledWith(
expect.anything(),
expect.anything(),
[
{
id: 'package-1',
name: 'package-1',
policy_id: 'policy_1',
policy_ids: ['policy_1', 'policy_2', 'mocked'],
},
]
);
});
});
describe('deployPolicy', () => {

View file

@ -831,32 +831,48 @@ class AgentPolicyService {
options
);
// Copy all package policies and append (copy n) to their names
if (baseAgentPolicy.package_policies) {
const newPackagePolicies = await pMap(
baseAgentPolicy.package_policies as PackagePolicy[],
async (packagePolicy: PackagePolicy) => {
const { id: packagePolicyId, version, ...newPackagePolicy } = packagePolicy;
// Copy non-shared package policies and append (copy n) to their names.
const basePackagePolicies = baseAgentPolicy.package_policies.filter(
(packagePolicy) => packagePolicy.policy_ids.length < 2
);
if (basePackagePolicies.length > 0) {
const newPackagePolicies = await pMap(
basePackagePolicies,
async (packagePolicy: PackagePolicy) => {
const { id: packagePolicyId, version, ...newPackagePolicy } = packagePolicy;
const updatedPackagePolicy = {
const updatedPackagePolicy = {
...newPackagePolicy,
name: await incrementPackagePolicyCopyName(soClient, packagePolicy.name),
};
return updatedPackagePolicy;
}
);
await packagePolicyService.bulkCreate(
soClient,
esClient,
newPackagePolicies.map((newPackagePolicy) => ({
...newPackagePolicy,
name: await incrementPackagePolicyCopyName(soClient, packagePolicy.name),
};
return updatedPackagePolicy;
}
);
await packagePolicyService.bulkCreate(
soClient,
esClient,
newPackagePolicies.map((newPackagePolicy) => ({
...newPackagePolicy,
policy_ids: [newAgentPolicy.id],
})),
{
...options,
bumpRevision: false,
}
policy_ids: [newAgentPolicy.id],
})),
{
...options,
bumpRevision: false,
}
);
}
// Link shared package policies to new agent policy.
const sharedBasePackagePolicies = baseAgentPolicy.package_policies.filter(
(packagePolicy) => packagePolicy.policy_ids.length > 1
);
if (sharedBasePackagePolicies.length > 0) {
const updatedSharedPackagePolicies = sharedBasePackagePolicies.map((packagePolicy) => ({
...packagePolicy,
policy_ids: [...packagePolicy.policy_ids, newAgentPolicy.id],
}));
await packagePolicyService.bulkUpdate(soClient, esClient, updatedSharedPackagePolicies);
}
}
// Tamper protection is dependent on endpoint package policy