Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fleet] Support space_id in preconfiguration #183920

Merged
merged 6 commits into from
May 23, 2024
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 () => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
it('should used a namespaced saved objet client if the agent policy space_id is set', async () => {
it('should used a namespaced saved object 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
);
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to add another test for changing the space_id for the same policy id

when I tested manually, it just created another policy in the second space, rather than "moving" the original policy, because space_id becomes part of the saved object ID. I think this is ok but want to make sure

});

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
nchaulet marked this conversation as resolved.
Show resolved Hide resolved
);

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');
jen-huang marked this conversation as resolved.
Show resolved Hide resolved
}
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