[Fleet] flag package policy SO to trigger agent policy bump (#200536)

## Summary

Closes https://github.com/elastic/kibana/issues/193352

Update:

Using a new SO field `bump_agent_policy_revision` in package policy type
to mark package policies for update, this will trigger an agent policy
revision bump.

The feature supports both legacy and new package policy SO types, and
queries policies from all spaces.

To test, add a model version change to the package policy type and save.
After Fleet setup is run, the agent policies using the package policies
should be bumped and deployed.
The same effect can be achieved by manually updating a package policy SO
and loading Fleet UI to trigger setup.
```
        '2': {
          changes: [
            {
              type: 'data_backfill',
              backfillFn: (doc) => {
                return { attributes: { ...doc.attributes, bump_agent_policy_revision: true } };
              },
            },
          ],
        },

  curl -sk -XPOST --user fleet_superuser:password -H 'content-type:application/json' \     -H'x-elastic-product-origin:fleet' \
     http://localhost:9200/.kibana_ingest/_update_by_query -d '
     { "query": {
      "match": {
        "type": "fleet-package-policies"
      }
    },"script": {
      "source": "ctx._source[\"fleet-package-policies\"].bump_agent_policy_revision = true",
      "lang": "painless"
    }
  }'

```

```
[2024-11-20T14:40:30.064+01:00][INFO ][plugins.fleet] Found 1 package policies that need agent policy revision bump
[2024-11-20T14:40:31.933+01:00][DEBUG][plugins.fleet] Updated 1 package policies in space space1 in 1869ms, bump 1 agent policies
[2024-11-20T14:40:35.056+01:00][DEBUG][plugins.fleet] Deploying 1 policies
[2024-11-20T14:40:35.493+01:00][DEBUG][plugins.fleet] Deploying policies: 7f108cf2-4cf0-4a11-8df4-fc69d00a3484:10
```

TODO:
- the same flag has to be added on agent policy and output types, and
the task extended to update them
  - I plan to do this in another pr, so that this doesn't become too big
- add integration test if possible

### Scale testing
Tested with 500 agent policies split to 2 spaces, 1 integration per
policy and bumping the flag in a new saved object model version, the
bump task took about 6s.
The deploy policies step is async, took about 30s.
```
[2024-11-20T15:53:55.628+01:00][INFO ][plugins.fleet] Found 501 package policies that need agent policy revision bump
[2024-11-20T15:53:57.881+01:00][DEBUG][plugins.fleet] Updated 250 package policies in space space1 in 2253ms, bump 250 agent policies
[2024-11-20T15:53:59.926+01:00][DEBUG][plugins.fleet] Updated 251 package policies in space default in 4298ms, bump 251 agent policies
[2024-11-20T15:54:01.186+01:00][DEBUG][plugins.fleet] Deploying 250 policies

[2024-11-20T15:54:29.989+01:00][DEBUG][plugins.fleet] Deploying policies: test-policy-space1-1:4, ...
[2024-11-20T15:54:33.538+01:00][DEBUG][plugins.fleet] Deploying policies: policy-elastic-agent-on-cloud:4, test-policy-default-1:4, ...

```

### 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

---------

Co-authored-by: kibanamachine <42973632+kibanamachine@users.noreply.github.com>
This commit is contained in:
Julia Bardi 2024-11-25 10:59:18 +01:00 committed by GitHub
parent a41017e74e
commit 973c69533b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
23 changed files with 392 additions and 89 deletions

View file

@ -36623,6 +36623,7 @@
"type": "boolean"
},
"use_space_awareness_migration_started_at": {
"nullable": true,
"type": "string"
},
"use_space_awareness_migration_status": {
@ -36824,6 +36825,7 @@
"type": "boolean"
},
"use_space_awareness_migration_started_at": {
"nullable": true,
"type": "string"
},
"use_space_awareness_migration_status": {

View file

@ -36623,6 +36623,7 @@
"type": "boolean"
},
"use_space_awareness_migration_started_at": {
"nullable": true,
"type": "string"
},
"use_space_awareness_migration_status": {
@ -36824,6 +36825,7 @@
"type": "boolean"
},
"use_space_awareness_migration_started_at": {
"nullable": true,
"type": "string"
},
"use_space_awareness_migration_status": {

View file

@ -29839,6 +29839,7 @@ paths:
secret_storage_requirements_met:
type: boolean
use_space_awareness_migration_started_at:
nullable: true
type: string
use_space_awareness_migration_status:
enum:
@ -29972,6 +29973,7 @@ paths:
secret_storage_requirements_met:
type: boolean
use_space_awareness_migration_started_at:
nullable: true
type: string
use_space_awareness_migration_status:
enum:

View file

@ -32607,6 +32607,7 @@ paths:
secret_storage_requirements_met:
type: boolean
use_space_awareness_migration_started_at:
nullable: true
type: string
use_space_awareness_migration_status:
enum:
@ -32739,6 +32740,7 @@ paths:
secret_storage_requirements_met:
type: boolean
use_space_awareness_migration_started_at:
nullable: true
type: string
use_space_awareness_migration_status:
enum:

View file

@ -511,6 +511,7 @@
],
"fleet-message-signing-keys": [],
"fleet-package-policies": [
"bump_agent_policy_revision",
"created_at",
"created_by",
"description",
@ -692,6 +693,7 @@
"version"
],
"ingest-package-policies": [
"bump_agent_policy_revision",
"created_at",
"created_by",
"description",

View file

@ -1715,6 +1715,9 @@
},
"fleet-package-policies": {
"properties": {
"bump_agent_policy_revision": {
"type": "boolean"
},
"created_at": {
"type": "date"
},
@ -2300,6 +2303,9 @@
},
"ingest-package-policies": {
"properties": {
"bump_agent_policy_revision": {
"type": "boolean"
},
"created_at": {
"type": "date"
},

View file

@ -108,7 +108,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"fleet-agent-policies": "f57d3b70e4175a19a18f18ee72a379ceec82e1fc",
"fleet-fleet-server-host": "69be15f6b6f2a2875ad3c7050ddea7a87f505417",
"fleet-message-signing-keys": "93421f43fed2526b59092a4e3c65d64bc2266c0f",
"fleet-package-policies": "2f4d524adb49a5281d3af0b66bb3003ba0ff2e44",
"fleet-package-policies": "8be2cabfed89e103e0d413f2900e9cf6cd31bc68",
"fleet-preconfiguration-deletion-record": "c52ea1e13c919afe8a5e8e3adbb7080980ecc08e",
"fleet-proxy": "6cb688f0d2dd856400c1dbc998b28704ff70363d",
"fleet-setup-lock": "0dc784792c79b5af5a6e6b5dcac06b0dbaa90bde",
@ -124,7 +124,7 @@ describe('checking migration metadata changes on all registered SO types', () =>
"ingest-agent-policies": "5e95e539826a40ad08fd0c1d161da0a4d86ffc6d",
"ingest-download-sources": "279a68147e62e4d8858c09ad1cf03bd5551ce58d",
"ingest-outputs": "55988d5f778bbe0e76caa7e6468707a0a056bdd8",
"ingest-package-policies": "53a94064674835fdb35e5186233bcd7052eabd22",
"ingest-package-policies": "dfa7b1045a2667a822181f40f012786724492439",
"ingest_manager_settings": "111a616eb72627c002029c19feb9e6c439a10505",
"inventory-view": "b8683c8e352a286b4aca1ab21003115a4800af83",
"kql-telemetry": "93c1d16c1a0dfca9c8842062cf5ef8f62ae401ad",

View file

@ -15,7 +15,7 @@ xpack.fleet.enableExperimental: ['useSpaceAwareness', 'subfeaturePrivileges']
After the feature flag is enabled you will have to do another step to opt-in for the feature, that call will migrate the current space agnostic saved objects to new space aware saved objects.
```shell
curl -u elastic:changeme -XPOST "http://localhost:5601/internal/fleet/enable_space_awareness" -H "kbn-xsrf: reporting" -H 'elastic-api-version: 1'
curl -u elastic:changeme -XPOST "http://localhost:5601/internal/fleet/enable_space_awareness" -H "kbn-xsrf: reporting" -H 'elastic-api-version: 1' -H 'x-elastic-internal-origin: 1'
```
## Space aware entities in Fleet

View file

@ -9,8 +9,7 @@ import { ToolingLog } from '@kbn/tooling-log';
import yargs from 'yargs';
import { chunk } from 'lodash';
import { LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE } from '../../common/constants';
import { LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common';
import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../../common/constants';
import { packagePolicyFixture } from './fixtures';
@ -30,20 +29,18 @@ const printUsage = () =>
const INDEX_BULK_OP = '{ "index":{ "_id": "{{id}}" } }\n';
const space = 'default';
function getPolicyId(idx: number | string) {
return `test-policy-${idx}`;
return `test-policy-${space}-${idx}`;
}
async function createAgentPoliciesDocsBulk(range: number[]) {
const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64');
const body = range
.flatMap((idx) => [
INDEX_BULK_OP.replace(
/{{id}}/,
`${LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE}:${getPolicyId(idx)}`
),
INDEX_BULK_OP.replace(/{{id}}/, `${AGENT_POLICY_SAVED_OBJECT_TYPE}:${getPolicyId(idx)}`),
JSON.stringify({
[LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE]: {
[AGENT_POLICY_SAVED_OBJECT_TYPE]: {
namespace: 'default',
monitoring_enabled: ['logs', 'metrics', 'traces'],
name: `Test Policy ${idx}`,
@ -60,11 +57,11 @@ async function createAgentPoliciesDocsBulk(range: number[]) {
schema_version: '1.1.1',
is_protected: false,
},
type: LEGACY_AGENT_POLICY_SAVED_OBJECT_TYPE,
namespaces: [space],
type: AGENT_POLICY_SAVED_OBJECT_TYPE,
references: [],
managed: false,
coreMigrationVersion: '8.8.0',
typeMigrationVersion: '10.3.0',
created_at: new Date().toISOString(),
updated_at: new Date().toISOString(),
}) + '\n',
@ -81,7 +78,7 @@ async function createAgentPoliciesDocsBulk(range: number[]) {
const data = await res.json();
if (!data.items) {
logger.error('Error creating agent policies docs: ' + JSON.stringify(data));
logger.error('Error creating agent policy docs: ' + JSON.stringify(data));
process.exit(1);
}
return data;
@ -91,14 +88,14 @@ async function createEnrollmentToken(range: number[]) {
const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64');
const body = range
.flatMap((idx) => [
INDEX_BULK_OP.replace(/{{id}}/, `test-enrollment-token-${idx}`),
INDEX_BULK_OP.replace(/{{id}}/, `test-enrollment-token-${space}-${idx}`),
JSON.stringify({
active: true,
api_key_id: 'faketest123',
api_key: 'test==',
name: `Test Policy ${idx}`,
policy_id: `${getPolicyId(idx)}`,
namespaces: [],
namespaces: [space],
created_at: new Date().toISOString(),
}) + '\n',
])
@ -115,7 +112,7 @@ async function createEnrollmentToken(range: number[]) {
const data = await res.json();
if (!data.items) {
logger.error('Error creating agent policies docs: ' + JSON.stringify(data));
logger.error('Error creating enrollment key docs: ' + JSON.stringify(data));
process.exit(1);
}
return data;
@ -125,14 +122,12 @@ async function createPackagePolicies(range: number[]) {
const auth = 'Basic ' + Buffer.from(ES_SUPERUSER + ':' + ES_PASSWORD).toString('base64');
const body = range
.flatMap((idx) => [
INDEX_BULK_OP.replace(
/{{id}}/,
`${LEGACY_PACKAGE_POLICY_SAVED_OBJECT_TYPE}:test-policy-${idx}`
),
INDEX_BULK_OP.replace(/{{id}}/, `fleet-package-policies:test-policy-${space}-${idx}`),
JSON.stringify(
packagePolicyFixture({
idx,
agentPolicyId: getPolicyId(idx),
space,
})
) + '\n',
])
@ -150,7 +145,7 @@ async function createPackagePolicies(range: number[]) {
const data = await res.json();
if (!data.items) {
logger.error('Error creating agent policies docs: ' + JSON.stringify(data));
logger.error('Error creating package policy docs: ' + JSON.stringify(data));
process.exit(1);
}
return data;

View file

@ -8,11 +8,13 @@
export const packagePolicyFixture = ({
agentPolicyId,
idx,
space,
}: {
idx: number;
agentPolicyId: string;
space: string;
}) => ({
'ingest-package-policies': {
'fleet-package-policies': {
name: `system-test-${idx}`,
namespace: '',
description: '',
@ -790,11 +792,12 @@ export const packagePolicyFixture = ({
updated_at: '2024-08-30T13:45:51.197Z',
updated_by: 'system',
},
type: 'ingest-package-policies',
namespaces: [space],
type: 'fleet-package-policies',
references: [],
managed: false,
coreMigrationVersion: '8.8.0',
typeMigrationVersion: '10.14.0',
typeMigrationVersion: '10.1.0',
updated_at: '2024-08-30T13:45:51.197Z',
created_at: '2024-08-30T13:45:51.197Z',
});

View file

@ -12,6 +12,6 @@ require('./create_agent_policies').run();
Usage:
cd x-pack/plugins/fleet
node scripts/create_agents/index.js
node scripts/create_agent_policies/index.js
*/

View file

@ -143,6 +143,7 @@ import { registerFieldsMetadataExtractors } from './services/register_fields_met
import { registerUpgradeManagedPackagePoliciesTask } from './services/setup/managed_package_policies';
import { registerDeployAgentPoliciesTask } from './services/agent_policies/deploy_agent_policies_task';
import { DeleteUnenrolledAgentsTask } from './tasks/delete_unenrolled_agents_task';
import { registerBumpAgentPoliciesTask } from './services/agent_policies/bump_agent_policies_task';
export interface FleetSetupDeps {
security: SecurityPluginSetup;
@ -619,6 +620,7 @@ export class FleetPlugin
// Register task
registerUpgradeManagedPackagePoliciesTask(deps.taskManager);
registerDeployAgentPoliciesTask(deps.taskManager);
registerBumpAgentPoliciesTask(deps.taskManager);
this.bulkActionsResolver = new BulkActionsResolver(deps.taskManager, core);
this.checkDeletedFilesTask = new CheckDeletedFilesTask({

View file

@ -619,6 +619,7 @@ export const getSavedObjectTypes = (
updated_by: { type: 'keyword' },
created_at: { type: 'date' },
created_by: { type: 'keyword' },
bump_agent_policy_revision: { type: 'boolean' },
},
},
modelVersions: {
@ -763,6 +764,16 @@ export const getSavedObjectTypes = (
},
],
},
'15': {
changes: [
{
type: 'mappings_addition',
addedMappings: {
bump_agent_policy_revision: { type: 'boolean' },
},
},
],
},
},
migrations: {
'7.10.0': migratePackagePolicyToV7100,
@ -823,6 +834,19 @@ export const getSavedObjectTypes = (
updated_by: { type: 'keyword' },
created_at: { type: 'date' },
created_by: { type: 'keyword' },
bump_agent_policy_revision: { type: 'boolean' },
},
},
modelVersions: {
'1': {
changes: [
{
type: 'mappings_addition',
addedMappings: {
bump_agent_policy_revision: { type: 'boolean' },
},
},
],
},
},
},

View file

@ -0,0 +1,115 @@
/*
* 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 { loggingSystemMock } from '@kbn/core/server/mocks';
import { agentPolicyService } from '../agent_policy';
import { appContextService } from '..';
import { getPackagePolicySavedObjectType } from '../package_policy';
import { _updatePackagePoliciesThatNeedBump } from './bump_agent_policies_task';
jest.mock('../app_context');
jest.mock('../agent_policy');
jest.mock('../package_policy');
const mockedAgentPolicyService = jest.mocked(agentPolicyService);
const mockedAppContextService = jest.mocked(appContextService);
const mockSoClient = {
find: jest.fn(),
bulkUpdate: jest.fn(),
} as any;
const mockGetPackagePolicySavedObjectType = jest.mocked(getPackagePolicySavedObjectType);
describe('_updatePackagePoliciesThatNeedBump', () => {
beforeEach(() => {
jest.clearAllMocks();
mockSoClient.find.mockResolvedValue({
total: 3,
saved_objects: [
{
id: 'packagePolicy1',
namespaces: ['default'],
attributes: {
policy_ids: ['policy1'],
},
},
{
id: 'packagePolicy12',
namespaces: ['default'],
attributes: {
policy_ids: ['policy1'],
},
},
{
id: 'packagePolicy2',
namespaces: ['space'],
attributes: {
policy_ids: ['policy2'],
},
},
{
id: 'packagePolicy3',
namespaces: ['space'],
attributes: {
policy_ids: ['policy3'],
},
},
],
page: 1,
perPage: 100,
});
mockedAppContextService.getInternalUserSOClientWithoutSpaceExtension.mockReturnValue(
mockSoClient
);
mockedAppContextService.getInternalUserSOClientForSpaceId.mockReturnValue(mockSoClient);
mockGetPackagePolicySavedObjectType.mockResolvedValue('fleet-package-policies');
});
it('should update package policy if bump agent policy revision needed', async () => {
const logger = loggingSystemMock.createLogger();
await _updatePackagePoliciesThatNeedBump(logger, () => false);
expect(mockSoClient.bulkUpdate).toHaveBeenCalledWith([
{
attributes: { bump_agent_policy_revision: false },
id: 'packagePolicy1',
type: 'fleet-package-policies',
},
{
attributes: { bump_agent_policy_revision: false },
id: 'packagePolicy12',
type: 'fleet-package-policies',
},
]);
expect(mockSoClient.bulkUpdate).toHaveBeenCalledWith([
{
attributes: { bump_agent_policy_revision: false },
id: 'packagePolicy2',
type: 'fleet-package-policies',
},
{
attributes: { bump_agent_policy_revision: false },
id: 'packagePolicy3',
type: 'fleet-package-policies',
},
]);
expect(mockedAgentPolicyService.bumpAgentPoliciesByIds).toHaveBeenCalledWith(
expect.anything(),
undefined,
['policy1']
);
expect(mockedAgentPolicyService.bumpAgentPoliciesByIds).toHaveBeenCalledWith(
expect.anything(),
undefined,
['policy2', 'policy3']
);
});
});

View file

@ -0,0 +1,139 @@
/*
* 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 { Logger } from '@kbn/core/server';
import type {
ConcreteTaskInstance,
TaskManagerSetupContract,
TaskManagerStartContract,
} from '@kbn/task-manager-plugin/server';
import { v4 as uuidv4 } from 'uuid';
import { uniq } from 'lodash';
import { DEFAULT_SPACE_ID } from '@kbn/spaces-plugin/common';
import { agentPolicyService, appContextService } from '..';
import { runWithCache } from '../epm/packages/cache';
import { getPackagePolicySavedObjectType } from '../package_policy';
import { mapPackagePolicySavedObjectToPackagePolicy } from '../package_policies';
import type { PackagePolicy, PackagePolicySOAttributes } from '../../types';
import { normalizeKuery } from '../saved_object';
import { SO_SEARCH_LIMIT } from '../../constants';
const TASK_TYPE = 'fleet:bump_agent_policies';
export function registerBumpAgentPoliciesTask(taskManagerSetup: TaskManagerSetupContract) {
taskManagerSetup.registerTaskDefinitions({
[TASK_TYPE]: {
title: 'Fleet Bump policies',
timeout: '5m',
maxAttempts: 3,
createTaskRunner: ({ taskInstance }: { taskInstance: ConcreteTaskInstance }) => {
let cancelled = false;
const isCancelled = () => cancelled;
return {
async run() {
if (isCancelled()) {
throw new Error('Task has been cancelled');
}
await runWithCache(async () => {
await _updatePackagePoliciesThatNeedBump(appContextService.getLogger(), isCancelled);
});
},
async cancel() {
cancelled = true;
},
};
},
},
});
}
async function getPackagePoliciesToBump(savedObjectType: string) {
const result = await appContextService
.getInternalUserSOClientWithoutSpaceExtension()
.find<PackagePolicySOAttributes>({
type: savedObjectType,
filter: normalizeKuery(savedObjectType, `${savedObjectType}.bump_agent_policy_revision:true`),
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
fields: ['id', 'namespaces', 'policy_ids'],
});
return {
total: result.total,
items: result.saved_objects.map((so) =>
mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces)
),
};
}
export async function _updatePackagePoliciesThatNeedBump(
logger: Logger,
isCancelled: () => boolean
) {
const savedObjectType = await getPackagePolicySavedObjectType();
const packagePoliciesToBump = await getPackagePoliciesToBump(savedObjectType);
logger.info(
`Found ${packagePoliciesToBump.total} package policies that need agent policy revision bump`
);
const packagePoliciesIndexedBySpace = packagePoliciesToBump.items.reduce((acc, policy) => {
const spaceId = policy.spaceIds?.[0] ?? DEFAULT_SPACE_ID;
if (!acc[spaceId]) {
acc[spaceId] = [];
}
acc[spaceId].push(policy);
return acc;
}, {} as { [k: string]: PackagePolicy[] });
const start = Date.now();
for (const [spaceId, packagePolicies] of Object.entries(packagePoliciesIndexedBySpace)) {
if (isCancelled()) {
throw new Error('Task has been cancelled');
}
const soClient = appContextService.getInternalUserSOClientForSpaceId(spaceId);
const esClient = appContextService.getInternalUserESClient();
await soClient.bulkUpdate<PackagePolicySOAttributes>(
packagePolicies.map((item) => ({
type: savedObjectType,
id: item.id,
attributes: {
bump_agent_policy_revision: false,
},
}))
);
const updatedCount = packagePolicies.length;
const agentPoliciesToBump = uniq(packagePolicies.map((item) => item.policy_ids).flat());
await agentPolicyService.bumpAgentPoliciesByIds(soClient, esClient, agentPoliciesToBump);
logger.debug(
`Updated ${updatedCount} package policies in space ${spaceId} in ${
Date.now() - start
}ms, bump ${agentPoliciesToBump.length} agent policies`
);
}
}
export async function scheduleBumpAgentPoliciesTask(taskManagerStart: TaskManagerStartContract) {
await taskManagerStart.ensureScheduled({
id: `${TASK_TYPE}:${uuidv4()}`,
scope: ['fleet'],
params: {},
taskType: TASK_TYPE,
runAt: new Date(Date.now() + 3 * 1000),
state: {},
});
}

View file

@ -1646,6 +1646,27 @@ class AgentPolicyService {
);
}
public async bumpAgentPoliciesByIds(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentPolicyIds: string[],
options?: { user?: AuthenticatedUser }
): Promise<SavedObjectsBulkUpdateResponse<AgentPolicy>> {
const internalSoClientWithoutSpaceExtension =
appContextService.getInternalUserSOClientWithoutSpaceExtension();
const savedObjectType = await getAgentPolicySavedObjectType();
const objects = agentPolicyIds.map((id: string) => ({ id, type: savedObjectType }));
const bulkGetResponse = await soClient.bulkGet<AgentPolicySOAttributes>(objects);
return this._bumpPolicies(
internalSoClientWithoutSpaceExtension,
esClient,
bulkGetResponse.saved_objects,
options
);
}
public async getInactivityTimeouts(): Promise<
Array<{ policyIds: string[]; inactivityTimeout: number }>
> {

View file

@ -28,17 +28,16 @@ import { licenseService } from '../license';
import { outputService } from '../output';
import { appContextService } from '../app_context';
export const mapPackagePolicySavedObjectToPackagePolicy = ({
id,
version,
attributes,
namespaces,
}: SavedObject<PackagePolicySOAttributes>): PackagePolicy => {
export const mapPackagePolicySavedObjectToPackagePolicy = (
{ id, version, attributes }: SavedObject<PackagePolicySOAttributes>,
namespaces?: string[]
): PackagePolicy => {
const { bump_agent_policy_revision: bumpAgentPolicyRevision, ...restAttributes } = attributes;
return {
id,
version,
spaceIds: namespaces,
...attributes,
...(namespaces ? { spaceIds: namespaces } : {}),
...restAttributes,
};
};

View file

@ -20,6 +20,7 @@ import type {
RequestHandlerContext,
SavedObjectsBulkCreateObject,
SavedObjectsBulkUpdateObject,
SavedObject,
} from '@kbn/core/server';
import { SavedObjectsUtils } from '@kbn/core/server';
import { v4 as uuidv4 } from 'uuid';
@ -446,7 +447,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
}
const createdPackagePolicy = { id: newSo.id, version: newSo.version, ...newSo.attributes };
const createdPackagePolicy = mapPackagePolicySavedObjectToPackagePolicy(newSo);
logger.debug(`Created new package policy with id ${newSo.id} and version ${newSo.version}`);
return packagePolicyService.runExternalCallbacks(
@ -668,11 +669,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
logger.debug(`Created new package policies`);
return {
created: newSos.map((newSo) => ({
id: newSo.id,
version: newSo.version,
...newSo.attributes,
})),
created: newSos.map((newSo) => mapPackagePolicySavedObjectToPackagePolicy(newSo)),
failed: failedPolicies,
};
}
@ -754,11 +751,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
}
const response = {
id: packagePolicySO.id,
version: packagePolicySO.version,
...packagePolicySO.attributes,
};
const response = mapPackagePolicySavedObjectToPackagePolicy(packagePolicySO);
// If possible, return the experimental features map for the package policy's `package` field
if (experimentalFeatures && response.package) {
@ -788,11 +781,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
return [];
}
const packagePolicies = packagePolicySO.saved_objects.map((so) => ({
id: so.id,
version: so.version,
...so.attributes,
}));
const packagePolicies = packagePolicySO.saved_objects.map((so) =>
mapPackagePolicySavedObjectToPackagePolicy(so)
);
for (const packagePolicy of packagePolicies) {
auditLoggingService.writeCustomSoAuditLog({
@ -835,11 +826,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
}
return {
id: so.id,
version: so.version,
...so.attributes,
};
return mapPackagePolicySavedObjectToPackagePolicy(so);
})
.filter((packagePolicy): packagePolicy is PackagePolicy => packagePolicy !== null);
@ -889,12 +876,9 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
}
return {
items: packagePolicies?.saved_objects.map((packagePolicySO) => ({
id: packagePolicySO.id,
version: packagePolicySO.version,
...packagePolicySO.attributes,
spaceIds: packagePolicySO.namespaces,
})),
items: packagePolicies?.saved_objects.map((so) =>
mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces)
),
total: packagePolicies?.total,
page,
perPage,
@ -1392,13 +1376,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
const updatedPoliciesSuccess = updatedPolicies
.filter((policy) => !policy.error && policy.attributes)
.map(
(soPolicy) =>
({
id: soPolicy.id,
version: soPolicy.version,
...soPolicy.attributes,
} as PackagePolicy)
.map((soPolicy) =>
mapPackagePolicySavedObjectToPackagePolicy(
soPolicy as SavedObject<PackagePolicySOAttributes>
)
);
return { updatedPolicies: updatedPoliciesSuccess, failedPolicies };
@ -2189,7 +2170,7 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
perPage: SO_SEARCH_LIMIT,
namespaces: ['*'],
})
).saved_objects.map(mapPackagePolicySavedObjectToPackagePolicy);
).saved_objects.map((so) => mapPackagePolicySavedObjectToPackagePolicy(so, so.namespaces));
if (packagePolicies.length > 0) {
const getPackagePolicyUpdate = (packagePolicy: PackagePolicy) => ({
@ -2306,7 +2287,10 @@ class PackagePolicyClientImpl implements PackagePolicyClient {
savedObjectType,
});
return mapPackagePolicySavedObjectToPackagePolicy(packagePolicySO);
return mapPackagePolicySavedObjectToPackagePolicy(
packagePolicySO,
packagePolicySO.namespaces
);
});
},
});

View file

@ -17,6 +17,7 @@ import { ensureAgentPoliciesFleetServerKeysAndPolicies } from './fleet_server_po
jest.mock('../app_context');
jest.mock('../agent_policy');
jest.mock('../api_keys');
jest.mock('../agent_policies/bump_agent_policies_task');
const mockedEnsureDefaultEnrollmentAPIKeyForAgentPolicy = jest.mocked(
ensureDefaultEnrollmentAPIKeyForAgentPolicy

View file

@ -13,6 +13,7 @@ import { ensureDefaultEnrollmentAPIKeyForAgentPolicy } from '../api_keys';
import { SO_SEARCH_LIMIT } from '../../constants';
import { appContextService } from '../app_context';
import { scheduleDeployAgentPoliciesTask } from '../agent_policies/deploy_agent_policies_task';
import { scheduleBumpAgentPoliciesTask } from '../agent_policies/bump_agent_policies_task';
export async function ensureAgentPoliciesFleetServerKeysAndPolicies({
logger,
@ -37,7 +38,6 @@ export async function ensureAgentPoliciesFleetServerKeysAndPolicies({
});
const outdatedAgentPolicyIds: Array<{ id: string; spaceId?: string }> = [];
await pMap(
agentPolicies,
async (agentPolicy) => {
@ -55,23 +55,23 @@ export async function ensureAgentPoliciesFleetServerKeysAndPolicies({
}
);
if (!outdatedAgentPolicyIds.length) {
return;
}
await scheduleBumpAgentPoliciesTask(appContextService.getTaskManagerStart()!);
if (appContextService.getExperimentalFeatures().asyncDeployPolicies) {
return scheduleDeployAgentPoliciesTask(
appContextService.getTaskManagerStart()!,
outdatedAgentPolicyIds
);
} else {
return agentPolicyService
.deployPolicies(
soClient,
outdatedAgentPolicyIds.map(({ id }) => id)
)
.catch((error) => {
logger.warn(`Error deploying policies: ${error.message}`, { error });
});
if (outdatedAgentPolicyIds.length) {
if (appContextService.getExperimentalFeatures().asyncDeployPolicies) {
return scheduleDeployAgentPoliciesTask(
appContextService.getTaskManagerStart()!,
outdatedAgentPolicyIds
);
} else {
return agentPolicyService
.deployPolicies(
soClient,
outdatedAgentPolicyIds.map(({ id }) => id)
)
.catch((error) => {
logger.warn(`Error deploying policies: ${error.message}`, { error });
});
}
}
}

View file

@ -61,7 +61,9 @@ export const SettingsResponseSchema = schema.object({
use_space_awareness_migration_status: schema.maybe(
schema.oneOf([schema.literal('pending'), schema.literal('success'), schema.literal('error')])
),
use_space_awareness_migration_started_at: schema.maybe(schema.string()),
use_space_awareness_migration_started_at: schema.maybe(
schema.oneOf([schema.literal(null), schema.string()])
),
delete_unenrolled_agents: schema.maybe(
schema.object({
enabled: schema.boolean(),

View file

@ -137,6 +137,7 @@ export interface PackagePolicySOAttributes {
};
agents?: number;
overrides?: any | null;
bump_agent_policy_revision?: boolean;
}
interface OutputSoBaseAttributes {

View file

@ -143,6 +143,7 @@ export default function ({ getService }: FtrProviderContext) {
'endpoint:metadata-check-transforms-task',
'endpoint:user-artifact-packager',
'entity_store:field_retention:enrichment',
'fleet:bump_agent_policies',
'fleet:check-deleted-files-task',
'fleet:delete-unenrolled-agents-task',
'fleet:deploy_agent_policies',