Skip to content

Commit

Permalink
[Fleet] Support kibana_namespace in preconfiguration (#183920)
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet authored May 23, 2024
1 parent e89b991 commit f6c2fec
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 25 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type InputsOverride = Partial<NewPackagePolicyInput> & {

export interface PreconfiguredAgentPolicy extends Omit<NewAgentPolicy, 'namespace' | 'id'> {
id: string | number;
space_id?: string;
namespace?: string;
package_policies: Array<
| (Partial<Omit<NewPackagePolicy, 'inputs' | 'package'>> & {
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/fleet/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
Expand Down Expand Up @@ -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,
},
Expand Down
6 changes: 5 additions & 1 deletion x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<NewAgentPolicy, 'namespace' | 'monitoring_enabled'> = {
namespace: 'default',
monitoring_enabled: ['logs', 'metrics'],
Expand Down
64 changes: 64 additions & 0 deletions x-pack/plugins/fleet/server/services/preconfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,7 @@ jest.mock('./app_context', () => ({
getExperimentalFeatures: jest.fn().mockReturnValue({
agentless: false,
}),
getInternalUserSOClientForSpaceId: jest.fn(),
},
}));

Expand Down Expand Up @@ -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', () => {
Expand Down
58 changes: 36 additions & 22 deletions x-pack/plugins/fleet/server/services/preconfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ interface PreconfigurationResult {
}

export async function ensurePreconfiguredPackagesAndPolicies(
soClient: SavedObjectsClientContract,
defaultSoClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
policies: PreconfiguredAgentPolicy[] = [],
packages: PreconfiguredPackage[] = [],
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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`);
Expand All @@ -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,
});
Expand Down Expand Up @@ -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,
Expand All @@ -194,22 +198,23 @@ export async function ensurePreconfiguredPackagesAndPolicies(
};
if (hasChanged) {
const updatedPolicy = await agentPolicyService.update(
soClient,
namespacedSoClient,
esClient,
String(preconfiguredAgentPolicy.id),
newFields,
{
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,
};
})
Expand All @@ -227,21 +232,25 @@ 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
);
const installedPackagePolicies = await Promise.all(
packagePolicies.map(async (preconfiguredPackagePolicy) => {
const { package: pkg, ...newPackagePolicy } = preconfiguredPackagePolicy;
const installedPackage = await getInstallation({
savedObjectsClient: soClient,
savedObjectsClient: defaultSoClient,
pkgName: pkg.name,
});
if (!installedPackage) {
Expand Down Expand Up @@ -272,7 +281,7 @@ export async function ensurePreconfiguredPackagesAndPolicies(
})
);
}
return { installedPackage, packagePolicy: newPackagePolicy };
return { installedPackage, packagePolicy: newPackagePolicy, namespacedSoClient };
})
);

Expand All @@ -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!,
Expand All @@ -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 },
Expand Down Expand Up @@ -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 {
Expand All @@ -348,11 +362,11 @@ export function comparePreconfiguredPolicyToCurrent(
}

async function addPreconfiguredPolicyPackages(
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
agentPolicy: AgentPolicy,
installedPackagePolicies: Array<{
installedPackage: Installation;
namespacedSoClient: SavedObjectsClientContract;
packagePolicy:
| (Partial<Omit<NewPackagePolicy, 'inputs'>> & {
id?: string | number;
Expand All @@ -369,13 +383,13 @@ async function addPreconfiguredPolicyPackages(
const packageInfoMap = new Map<string, PackageInfo>();

// 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,
});
Expand All @@ -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,
Expand All @@ -409,7 +423,7 @@ async function addPreconfiguredPolicyPackages(
{}
);

await packagePolicyService.create(soClient, esClient, newPackagePolicy, {
await packagePolicyService.create(namespacedSoClient, esClient, newPackagePolicy, {
id,
bumpRevision: bumpAgentPolicyRevison,
skipEnsureInstalled: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand Down

0 comments on commit f6c2fec

Please sign in to comment.