From f6c2fece84752a9b9b8489ae28326a789e6530c1 Mon Sep 17 00:00:00 2001 From: Nicolas Chaulet Date: Thu, 23 May 2024 08:44:47 -0400 Subject: [PATCH] [Fleet] Support kibana_namespace in preconfiguration (#183920) --- .../common/types/models/preconfiguration.ts | 1 + .../fleet/server/saved_objects/index.ts | 4 +- .../fleet/server/services/agent_policy.ts | 6 +- .../server/services/preconfiguration.test.ts | 64 +++++++++++++++++++ .../fleet/server/services/preconfiguration.ts | 58 ++++++++++------- .../server/types/models/preconfiguration.ts | 1 + 6 files changed, 109 insertions(+), 25 deletions(-) diff --git a/x-pack/plugins/fleet/common/types/models/preconfiguration.ts b/x-pack/plugins/fleet/common/types/models/preconfiguration.ts index 8363116bf3bb1..14d9bbed5513c 100644 --- a/x-pack/plugins/fleet/common/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/common/types/models/preconfiguration.ts @@ -24,6 +24,7 @@ export type InputsOverride = Partial & { export interface PreconfiguredAgentPolicy extends Omit { id: string | number; + space_id?: string; namespace?: string; package_policies: Array< | (Partial> & { diff --git a/x-pack/plugins/fleet/server/saved_objects/index.ts b/x-pack/plugins/fleet/server/saved_objects/index.ts index 457711b2646e7..8e058a3a3ff8f 100644 --- a/x-pack/plugins/fleet/server/saved_objects/index.ts +++ b/x-pack/plugins/fleet/server/saved_objects/index.ts @@ -587,7 +587,7 @@ export const getSavedObjectTypes = ( name: PACKAGES_SAVED_OBJECT_TYPE, indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, - namespaceType: useSpaceAwareness ? 'single' : 'agnostic', + namespaceType: 'agnostic', management: { importableAndExportable: false, }, @@ -680,7 +680,7 @@ export const getSavedObjectTypes = ( name: ASSETS_SAVED_OBJECT_TYPE, indexPattern: INGEST_SAVED_OBJECT_INDEX, hidden: false, - namespaceType: useSpaceAwareness ? 'single' : 'agnostic', + namespaceType: 'agnostic', management: { importableAndExportable: false, }, diff --git a/x-pack/plugins/fleet/server/services/agent_policy.ts b/x-pack/plugins/fleet/server/services/agent_policy.ts index afa7b7c5d92de..95de0d05e86b9 100644 --- a/x-pack/plugins/fleet/server/services/agent_policy.ts +++ b/x-pack/plugins/fleet/server/services/agent_policy.ts @@ -195,7 +195,11 @@ class AgentPolicyService { created: boolean; policy?: AgentPolicy; }> { - const { id, ...preconfiguredAgentPolicy } = omit(config, 'package_policies'); + const { + id, + space_id: kibanaSpaceId, + ...preconfiguredAgentPolicy + } = omit(config, 'package_policies'); const newAgentPolicyDefaults: Pick = { namespace: 'default', monitoring_enabled: ['logs', 'metrics'], diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts index 004d4b83a460a..f4e6eab5374c0 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.test.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.test.ts @@ -306,6 +306,7 @@ jest.mock('./app_context', () => ({ getExperimentalFeatures: jest.fn().mockReturnValue({ agentless: false, }), + getInternalUserSOClientForSpaceId: jest.fn(), }, })); @@ -1124,6 +1125,69 @@ describe('policy preconfiguration', () => { expect(policies[0].id).toBe('test-id'); expect(nonFatalErrorsB.length).toBe(0); }); + + it('should used a namespaced saved objet client if the agent policy space_id is set', async () => { + const TEST_NAMESPACE = 'test'; + const namespacedSOClient = getPutPreconfiguredPackagesMock(); + const soClient = getPutPreconfiguredPackagesMock(); + const esClient = elasticsearchServiceMock.createClusterClient().asInternalUser; + + jest + .mocked(appContextService) + .getInternalUserSOClientForSpaceId.mockReturnValue(namespacedSOClient); + + await ensurePreconfiguredPackagesAndPolicies( + soClient, + esClient, + [ + { + name: 'Test policy', + namespace: 'default', + id: 'test-id', + space_id: TEST_NAMESPACE, + is_managed: true, + package_policies: [ + { + package: { name: 'test_package' }, + name: 'test_package1', + }, + ], + }, + ] as PreconfiguredAgentPolicy[], + [{ name: 'test_package', version: '3.0.0' }], + mockDefaultOutput, + mockDefaultDownloadService, + DEFAULT_SPACE_ID + ); + + jest.mocked(appContextService.getExperimentalFeatures).mockReturnValue({ + agentless: true, + } as any); + + expect(appContextService.getInternalUserSOClientForSpaceId).toBeCalledTimes(1); + expect(appContextService.getInternalUserSOClientForSpaceId).toBeCalledWith(TEST_NAMESPACE); + + expect(mockedPackagePolicyService.create).toBeCalledTimes(1); + expect(mockedPackagePolicyService.create).toBeCalledWith( + namespacedSOClient, // namespaced so client + expect.anything(), // es client + expect.objectContaining({ + name: 'test_package1', + }), + expect.anything() // options + ); + + expect(spyAgentPolicyServiceUpdate).toBeCalledTimes(1); + expect(spyAgentPolicyServiceUpdate).toBeCalledWith( + namespacedSOClient, // namespaced so client + expect.anything(), // es client + expect.anything(), // id + expect.objectContaining({ + is_managed: true, + }), + expect.anything() // options + ); + }); }); describe('with bundled packages', () => { diff --git a/x-pack/plugins/fleet/server/services/preconfiguration.ts b/x-pack/plugins/fleet/server/services/preconfiguration.ts index 26f633cc8f021..04df94d89b636 100644 --- a/x-pack/plugins/fleet/server/services/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/services/preconfiguration.ts @@ -50,7 +50,7 @@ interface PreconfigurationResult { } export async function ensurePreconfiguredPackagesAndPolicies( - soClient: SavedObjectsClientContract, + defaultSoClient: SavedObjectsClientContract, esClient: ElasticsearchClient, policies: PreconfiguredAgentPolicy[] = [], packages: PreconfiguredPackage[] = [], @@ -96,7 +96,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( // Preinstall packages specified in Kibana config const preconfiguredPackages = await bulkInstallPackages({ - savedObjectsClient: soClient, + savedObjectsClient: defaultSoClient, esClient, packagesToInstall, force: true, // Always force outdated packages to be installed if a later version isn't installed @@ -127,7 +127,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( // will occur between upgrading the package and reinstalling the previously failed package. // By moving this outside of the Promise.all, the upgrade will occur first, and then we'll attempt to reinstall any // packages that are stuck in the installing state. - await ensurePackagesCompletedInstall(soClient, esClient); + await ensurePackagesCompletedInstall(defaultSoClient, esClient); // Create policies specified in Kibana config logger.debug(`Creating preconfigured policies`); @@ -140,7 +140,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( searchFields: ['id'], search: escapeSearchQueryPhrase(preconfigurationId), }; - const deletionRecords = await soClient.find({ + const deletionRecords = await defaultSoClient.find({ type: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, ...searchParams, }); @@ -174,14 +174,18 @@ export async function ensurePreconfiguredPackagesAndPolicies( ); } + const namespacedSoClient = preconfiguredAgentPolicy.space_id + ? appContextService.getInternalUserSOClientForSpaceId(preconfiguredAgentPolicy.space_id) + : defaultSoClient; + const { created, policy } = await agentPolicyService.ensurePreconfiguredAgentPolicy( - soClient, + namespacedSoClient, esClient, - omit(preconfiguredAgentPolicy, 'is_managed') // Don't add `is_managed` until the policy has been fully configured + omit(preconfiguredAgentPolicy, 'is_managed', 'space_id') // Don't add `is_managed` until the policy has been fully configured and not persist space_id ); if (!created) { - if (!policy) return { created, policy }; + if (!policy) return { created, policy, namespacedSoClient }; if (!policy.is_managed && !preconfiguredAgentPolicy.is_managed) return { created, policy }; const { hasChanged, fields } = comparePreconfiguredPolicyToCurrent( preconfiguredAgentPolicy, @@ -194,7 +198,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( }; if (hasChanged) { const updatedPolicy = await agentPolicyService.update( - soClient, + namespacedSoClient, esClient, String(preconfiguredAgentPolicy.id), newFields, @@ -202,14 +206,15 @@ export async function ensurePreconfiguredPackagesAndPolicies( force: true, } ); - return { created, policy: updatedPolicy }; + return { created, policy: updatedPolicy, namespacedSoClient }; } - return { created, policy }; + return { created, policy, namespacedSoClient }; } return { created, policy, + namespacedSoClient, shouldAddIsManagedFlag: preconfiguredAgentPolicy.is_managed, }; }) @@ -227,13 +232,17 @@ export async function ensurePreconfiguredPackagesAndPolicies( continue; } fulfilledPolicies.push(policyResult.value); - const { created, policy, shouldAddIsManagedFlag } = policyResult.value; + const { created, policy, shouldAddIsManagedFlag, namespacedSoClient } = policyResult.value; + if (created || policies[i].is_managed) { + if (!namespacedSoClient) { + throw new Error('No soClient created for that policy'); + } const preconfiguredAgentPolicy = policies[i]; const { package_policies: packagePolicies } = preconfiguredAgentPolicy; const agentPolicyWithPackagePolicies = await agentPolicyService.get( - soClient, + namespacedSoClient, policy!.id, true ); @@ -241,7 +250,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( packagePolicies.map(async (preconfiguredPackagePolicy) => { const { package: pkg, ...newPackagePolicy } = preconfiguredPackagePolicy; const installedPackage = await getInstallation({ - savedObjectsClient: soClient, + savedObjectsClient: defaultSoClient, pkgName: pkg.name, }); if (!installedPackage) { @@ -272,7 +281,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( }) ); } - return { installedPackage, packagePolicy: newPackagePolicy }; + return { installedPackage, packagePolicy: newPackagePolicy, namespacedSoClient }; }) ); @@ -287,7 +296,6 @@ export async function ensurePreconfiguredPackagesAndPolicies( logger.debug(`Adding preconfigured package policies ${packagePoliciesToAdd}`); const s = apm.startSpan('Add preconfigured package policies', 'preconfiguration'); await addPreconfiguredPolicyPackages( - soClient, esClient, policy!, packagePoliciesToAdd!, @@ -299,7 +307,7 @@ export async function ensurePreconfiguredPackagesAndPolicies( // Add the is_managed flag after configuring package policies to avoid errors if (shouldAddIsManagedFlag) { await agentPolicyService.update( - soClient, + namespacedSoClient, esClient, policy!.id, { is_managed: true }, @@ -338,7 +346,13 @@ export function comparePreconfiguredPolicyToCurrent( ) { // Namespace is omitted from being compared because even for managed policies, we still // want users to be able to pick their own namespace: https://github.com/elastic/kibana/issues/110533 - const configTopLevelFields = omit(policyFromConfig, 'package_policies', 'id', 'namespace'); + const configTopLevelFields = omit( + policyFromConfig, + 'package_policies', + 'id', + 'namespace', + 'space_id' + ); const currentTopLevelFields = pick(currentPolicy, ...Object.keys(configTopLevelFields)); return { @@ -348,11 +362,11 @@ export function comparePreconfiguredPolicyToCurrent( } async function addPreconfiguredPolicyPackages( - soClient: SavedObjectsClientContract, esClient: ElasticsearchClient, agentPolicy: AgentPolicy, installedPackagePolicies: Array<{ installedPackage: Installation; + namespacedSoClient: SavedObjectsClientContract; packagePolicy: | (Partial> & { id?: string | number; @@ -369,13 +383,13 @@ async function addPreconfiguredPolicyPackages( const packageInfoMap = new Map(); // Add packages synchronously to avoid overwriting - for (const { installedPackage, packagePolicy } of installedPackagePolicies) { + for (const { installedPackage, packagePolicy, namespacedSoClient } of installedPackagePolicies) { let packageInfo: PackageInfo; if (packageInfoMap.has(installedPackage.name)) { packageInfo = packageInfoMap.get(installedPackage.name)!; } else { packageInfo = await getPackageInfo({ - savedObjectsClient: soClient, + savedObjectsClient: namespacedSoClient, pkgName: installedPackage.name, pkgVersion: installedPackage.version, }); @@ -384,7 +398,7 @@ async function addPreconfiguredPolicyPackages( if (Array.isArray(packagePolicy.inputs)) { const { id, name, description, inputs } = packagePolicy; await addPackageToAgentPolicy( - soClient, + namespacedSoClient, esClient, agentPolicy, packageInfo, @@ -409,7 +423,7 @@ async function addPreconfiguredPolicyPackages( {} ); - await packagePolicyService.create(soClient, esClient, newPackagePolicy, { + await packagePolicyService.create(namespacedSoClient, esClient, newPackagePolicy, { id, bumpRevision: bumpAgentPolicyRevison, skipEnsureInstalled: true, diff --git a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts index ce8fb7df3937d..090d74b38a412 100644 --- a/x-pack/plugins/fleet/server/types/models/preconfiguration.ts +++ b/x-pack/plugins/fleet/server/types/models/preconfiguration.ts @@ -134,6 +134,7 @@ export const PreconfiguredFleetProxiesSchema = schema.arrayOf( export const PreconfiguredAgentPoliciesSchema = schema.arrayOf( schema.object({ ...AgentPolicyBaseSchema, + space_id: schema.maybe(schema.string()), namespace: schema.maybe(AgentPolicyNamespaceSchema), id: schema.maybe(schema.oneOf([schema.string(), schema.number()])), is_default: schema.maybe(schema.boolean()),