Skip to content

Commit

Permalink
[Fleet] backfill agentless package policies with supports_agentless
Browse files Browse the repository at this point in the history
… field (elastic#204410)

## Summary

Closes elastic#203821

Added a function to Fleet setup to query package policies that are
missing `supports_agentless` field and backfilling them. Only doing this
for `cloud_security_posture` package, to skip other non-related packages
like `system`.

To verify:
- follow the steps in the description here to create an agentless agent
policy with cspm integration:
elastic#199567
- manually update the package policy to simulate
`supports_agentless:false`
- trigger Fleet setup
- verify that the cspm package policy has `supports_agentless:true`

```
PUT kbn:/api/fleet/package_policies/<policy_id>
{
   "supports_agentless": false
}

POST kbn:/api/fleet/setup

GET kbn:/api/fleet/package_policies/<policy_id>
```

Logs:
```
[2024-12-16T15:42:11.027+01:00][DEBUG][plugins.fleet] Backfilling package policy supports_agentless field
[2024-12-16T15:42:11.034+01:00][DEBUG][plugins.fleet] Backfilling supports_agentless on package policies: 6a06d167-e02e-4057-9d71-e1f7e5dd2847
[2024-12-16T15:42:11.035+01:00][DEBUG][plugins.fleet] Starting update of package policy 6a06d167-e02e-4057-9d71-e1f7e5dd2847
[2024-12-16T15:42:13.213+01:00][DEBUG][plugins.fleet] Deploying policies: 0ed942d5-6c01-484f-a1c5-6c7fff92b020:12
[2024-12-16T15:42:13.610+01:00][DEBUG][plugins.fleet] Agent policy 0ed942d5-6c01-484f-a1c5-6c7fff92b020 update completed, revision: 12
[2024-12-16T15:42:13.610+01:00][DEBUG][plugins.fleet] Package policy 6a06d167-e02e-4057-9d71-e1f7e5dd2847 update completed
```

### Checklist

- [x] [Unit or functional
tests](https://www.elastic.co/guide/en/kibana/master/development-tests.html)
were updated or added to match the most common scenarios

(cherry picked from commit a229d7a)
  • Loading branch information
juliaElastic committed Dec 17, 2024
1 parent ab308aa commit 914b800
Show file tree
Hide file tree
Showing 4 changed files with 170 additions and 0 deletions.
66 changes: 66 additions & 0 deletions x-pack/plugins/fleet/server/services/backfill_agentless.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
/*
* 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 { backfillPackagePolicySupportsAgentless } from './backfill_agentless';
import { packagePolicyService } from './package_policy';

jest.mock('.', () => ({
appContextService: {
getLogger: () => ({
debug: jest.fn(),
}),
getInternalUserSOClientForSpaceId: jest.fn(),
getInternalUserSOClientWithoutSpaceExtension: () => ({
find: jest.fn().mockImplementation((options) => {
if (options.type === 'ingest-agent-policies') {
return {
saved_objects: [{ id: 'agent_policy_1' }, { id: 'agent_policy_2' }],
};
} else {
return {
saved_objects: [
{
id: 'package_policy_1',
attributes: {
inputs: [],
policy_ids: ['agent_policy_1'],
supports_agentless: false,
},
},
],
};
}
}),
}),
},
}));

jest.mock('./package_policy', () => ({
packagePolicyService: {
update: jest.fn(),
},
getPackagePolicySavedObjectType: jest.fn().mockResolvedValue('ingest-package-policies'),
}));

describe('backfill agentless package policies', () => {
it('should backfill package policies missing supports_agentless', async () => {
await backfillPackagePolicySupportsAgentless(undefined as any);

expect(packagePolicyService.update).toHaveBeenCalledWith(
undefined,
undefined,
'package_policy_1',
{
enabled: undefined,
inputs: [],
name: undefined,
policy_ids: ['agent_policy_1'],
supports_agentless: true,
}
);
});
});
99 changes: 99 additions & 0 deletions x-pack/plugins/fleet/server/services/backfill_agentless.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
/*
* 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 type { ElasticsearchClient } from '@kbn/core/server';

import pMap from 'p-map';

import { MAX_CONCURRENT_AGENT_POLICIES_OPERATIONS, SO_SEARCH_LIMIT } from '../constants';

import type { AgentPolicySOAttributes, PackagePolicy, PackagePolicySOAttributes } from '../types';

import { getAgentPolicySavedObjectType } from './agent_policy';

import { appContextService } from '.';
import { getPackagePolicySavedObjectType, packagePolicyService } from './package_policy';
import { mapPackagePolicySavedObjectToPackagePolicy } from './package_policies';

export async function backfillPackagePolicySupportsAgentless(esClient: ElasticsearchClient) {
const apSavedObjectType = await getAgentPolicySavedObjectType();
const internalSoClientWithoutSpaceExtension =
appContextService.getInternalUserSOClientWithoutSpaceExtension();
const findRes = await internalSoClientWithoutSpaceExtension.find<AgentPolicySOAttributes>({
type: apSavedObjectType,
page: 1,
perPage: SO_SEARCH_LIMIT,
filter: `${apSavedObjectType}.attributes.supports_agentless:true`,
fields: [`id`],
namespaces: ['*'],
});

const agentPolicyIds = findRes.saved_objects.map((so) => so.id);

if (agentPolicyIds.length === 0) {
return;
}

const savedObjectType = await getPackagePolicySavedObjectType();
const packagePoliciesToUpdate = (
await appContextService
.getInternalUserSOClientWithoutSpaceExtension()
.find<PackagePolicySOAttributes>({
type: savedObjectType,
fields: [
'name',
'policy_ids',
'supports_agentless',
'enabled',
'policy_ids',
'inputs',
'package',
],
filter: `${savedObjectType}.attributes.package.name:cloud_security_posture AND (NOT ${savedObjectType}.attributes.supports_agentless:true) AND ${savedObjectType}.attributes.policy_ids:(${agentPolicyIds.join(
' OR '
)})`,
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
})
).saved_objects.map((so) => mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces));

appContextService
.getLogger()
.debug(
`Backfilling supports_agentless on package policies: ${packagePoliciesToUpdate.map(
(policy) => policy.id
)}`
);

if (packagePoliciesToUpdate.length > 0) {
const getPackagePolicyUpdate = (packagePolicy: PackagePolicy) => ({
name: packagePolicy.name,
enabled: packagePolicy.enabled,
policy_ids: packagePolicy.policy_ids,
inputs: packagePolicy.inputs,
supports_agentless: true,
});

await pMap(
packagePoliciesToUpdate,
(packagePolicy) => {
const soClient = appContextService.getInternalUserSOClientForSpaceId(
packagePolicy.spaceIds?.[0]
);
return packagePolicyService.update(
soClient,
esClient,
packagePolicy.id,
getPackagePolicyUpdate(packagePolicy)
);
},
{
concurrency: MAX_CONCURRENT_AGENT_POLICIES_OPERATIONS,
}
);
}
}
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/services/setup.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ jest.mock('./epm/elasticsearch/template/install', () => {
...jest.requireActual('./epm/elasticsearch/template/install'),
};
});
jest.mock('./backfill_agentless');

const mockedMethodThrowsError = (mockFn: jest.Mock) =>
mockFn.mockImplementation(() => {
Expand Down
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/server/services/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import {
ensureDeleteUnenrolledAgentsSetting,
getPreconfiguredDeleteUnenrolledAgentsSettingFromConfig,
} from './preconfiguration/delete_unenrolled_agent_setting';
import { backfillPackagePolicySupportsAgentless } from './backfill_agentless';

export interface SetupStatus {
isInitialized: boolean;
Expand Down Expand Up @@ -300,6 +301,9 @@ async function createSetupSideEffects(
await ensureAgentPoliciesFleetServerKeysAndPolicies({ soClient, esClient, logger });
stepSpan?.end();

logger.debug('Backfilling package policy supports_agentless field');
await backfillPackagePolicySupportsAgentless(esClient);

const nonFatalErrors = [
...preconfiguredPackagesNonFatalErrors,
...(messageSigningServiceNonFatalError ? [messageSigningServiceNonFatalError] : []),
Expand Down

0 comments on commit 914b800

Please sign in to comment.