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] Add preconfiguration to kibana config #96588

Merged
merged 14 commits into from
Apr 14, 2021
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,8 @@ kibana_vars=(
xpack.fleet.agents.elasticsearch.host
xpack.fleet.agents.kibana.host
xpack.fleet.agents.tlsCheckDisabled
xpack.fleet.agentPolicies
xpack.fleet.packages
xpack.fleet.registryUrl
xpack.graph.canEditDrillDownUrls
xpack.graph.enabled
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/common/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export * from './epm';
export * from './output';
export * from './enrollment_api_key';
export * from './settings';
export * from './preconfiguration';

// TODO: This is the default `index.max_result_window` ES setting, which dictates
// the maximum amount of results allowed to be returned from a search. It's possible
Expand Down
9 changes: 9 additions & 0 deletions x-pack/plugins/fleet/common/constants/preconfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*
* 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.
*/

export const PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE =
'fleet-preconfiguration-deletion-record';
3 changes: 3 additions & 0 deletions x-pack/plugins/fleet/common/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

export * from './models';
export * from './rest_spec';
import type { PreconfiguredAgentPolicy, PreconfiguredPackage } from './models/preconfiguration';

export interface FleetConfigType {
enabled: boolean;
Expand All @@ -32,6 +33,8 @@ export interface FleetConfigType {
agentPolicyRolloutRateLimitIntervalMs: number;
agentPolicyRolloutRateLimitRequestPerInterval: number;
};
agentPolicies?: PreconfiguredAgentPolicy[];
packages?: PreconfiguredPackage[];
}

// Calling Object.entries(PackagesGroupedByStatus) gave `status: string`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,5 @@

export interface PostIngestSetupResponse {
isInitialized: boolean;
preconfigurationError?: { name: string; message: string };
}
11 changes: 10 additions & 1 deletion x-pack/plugins/fleet/public/applications/fleet/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
sendSetup,
useBreadcrumbs,
useConfig,
useStartServices,
} from './hooks';
import { Error, Loading } from './components';
import { IntraAppStateProvider } from './hooks/use_intra_app_state';
Expand Down Expand Up @@ -59,6 +60,7 @@ const Panel = styled(EuiPanel)`

export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
useBreadcrumbs('base');
const { notifications } = useStartServices();

const [isPermissionsLoading, setIsPermissionsLoading] = useState<boolean>(false);
const [permissionsError, setPermissionsError] = useState<string>();
Expand All @@ -81,6 +83,13 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
if (setupResponse.error) {
setInitializationError(setupResponse.error);
}
if (setupResponse.data.preconfigurationError) {
notifications.toasts.addError(setupResponse.data.preconfigurationError, {
title: i18n.translate('xpack.fleet.setup.uiPreconfigurationErrorTitle', {
defaultMessage: 'Configuration error',
}),
});
}
} catch (err) {
setInitializationError(err);
}
Expand All @@ -92,7 +101,7 @@ export const WithPermissionsAndSetup: React.FC = memo(({ children }) => {
setPermissionsError('REQUEST_ERROR');
}
})();
}, []);
}, [notifications.toasts]);

if (isPermissionsLoading || permissionsError) {
return (
Expand Down
1 change: 1 addition & 0 deletions x-pack/plugins/fleet/server/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,5 @@ export {
// Fleet Server index
ENROLLMENT_API_KEYS_INDEX,
AGENTS_INDEX,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
} from '../../common';
4 changes: 4 additions & 0 deletions x-pack/plugins/fleet/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import {
AGENT_POLLING_REQUEST_TIMEOUT_MS,
} from '../common';

import { PreconfiguredPackagesSchema, PreconfiguredAgentPoliciesSchema } from './types';

import { FleetPlugin } from './plugin';

export { default as apm } from 'elastic-apm-node';
Expand Down Expand Up @@ -77,6 +79,8 @@ export const config: PluginConfigDescriptor = {
defaultValue: AGENT_POLICY_ROLLOUT_RATE_LIMIT_REQUEST_PER_INTERVAL,
}),
}),
packages: schema.maybe(PreconfiguredPackagesSchema),
agentPolicies: schema.maybe(PreconfiguredAgentPoliciesSchema),
}),
};

Expand Down
2 changes: 2 additions & 0 deletions x-pack/plugins/fleet/server/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
AGENT_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
} from './constants';
import { registerSavedObjects, registerEncryptedSavedObjects } from './saved_objects';
import {
Expand Down Expand Up @@ -133,6 +134,7 @@ const allSavedObjectTypes = [
AGENT_SAVED_OBJECT_TYPE,
AGENT_EVENT_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
];

/**
Expand Down
4 changes: 3 additions & 1 deletion x-pack/plugins/fleet/server/routes/setup/handlers.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ describe('FleetSetupHandler', () => {
});

it('POST /setup succeeds w/200 and body of resolved value', async () => {
mockSetupIngestManager.mockImplementation(() => Promise.resolve({ isIntialized: true }));
mockSetupIngestManager.mockImplementation(() =>
Promise.resolve({ isInitialized: true, preconfigurationError: undefined })
);
await FleetSetupHandler(context, request, response);

const expectedBody: PostIngestSetupResponse = { isInitialized: true };
Expand Down
7 changes: 3 additions & 4 deletions x-pack/plugins/fleet/server/routes/setup/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,13 +63,13 @@ export const createFleetSetupHandler: RequestHandler<
try {
const soClient = context.core.savedObjects.client;
const esClient = context.core.elasticsearch.client.asCurrentUser;
await setupIngestManager(soClient, esClient);
const body = await setupIngestManager(soClient, esClient);
await setupFleet(soClient, esClient, {
forceRecreate: request.body?.forceRecreate ?? false,
});

return response.ok({
body: { isInitialized: true },
body,
});
} catch (error) {
return defaultIngestErrorHandler({ error, response });
Expand All @@ -81,8 +81,7 @@ export const FleetSetupHandler: RequestHandler = async (context, request, respon
const esClient = context.core.elasticsearch.client.asCurrentUser;

try {
const body: PostIngestSetupResponse = { isInitialized: true };
await setupIngestManager(soClient, esClient);
const body: PostIngestSetupResponse = await setupIngestManager(soClient, esClient);
return response.ok({
body,
});
Expand Down
14 changes: 14 additions & 0 deletions x-pack/plugins/fleet/server/saved_objects/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
AGENT_ACTION_SAVED_OBJECT_TYPE,
ENROLLMENT_API_KEYS_SAVED_OBJECT_TYPE,
GLOBAL_SETTINGS_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
} from '../constants';

import {
Expand Down Expand Up @@ -358,6 +359,19 @@ const getSavedObjectTypes = (
},
},
},
[PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE]: {
name: PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
hidden: false,
namespaceType: 'agnostic',
management: {
importableAndExportable: false,
},
mappings: {
properties: {
preconfiguration_id: { type: 'keyword' },
},
},
},
});

export function registerSavedObjects(
Expand Down
11 changes: 10 additions & 1 deletion x-pack/plugins/fleet/server/services/agent_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import {
DEFAULT_AGENT_POLICY,
AGENT_POLICY_SAVED_OBJECT_TYPE,
AGENT_SAVED_OBJECT_TYPE,
PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE,
} from '../constants';
import type {
PackagePolicy,
Expand Down Expand Up @@ -150,7 +151,7 @@ class AgentPolicyService {
config: PreconfiguredAgentPolicy
): Promise<{
created: boolean;
policy: AgentPolicy;
policy?: AgentPolicy;
}> {
const { id, ...preconfiguredAgentPolicy } = omit(config, 'package_policies');
const preconfigurationId = String(id);
Expand Down Expand Up @@ -582,6 +583,13 @@ class AgentPolicyService {
}
);
}

if (agentPolicy.preconfiguration_id) {
await soClient.create(PRECONFIGURATION_DELETION_RECORD_SAVED_OBJECT_TYPE, {
preconfiguration_id: String(agentPolicy.preconfiguration_id),
});
}

await soClient.delete(SAVED_OBJECT_TYPE, id);
await this.triggerAgentPolicyUpdatedEvent(soClient, esClient, 'deleted', id);
return {
Expand Down Expand Up @@ -819,5 +827,6 @@ export async function addPackageToAgentPolicy(

await packagePolicyService.create(soClient, esClient, newPackagePolicy, {
bumpRevision: false,
skipEnsureInstalled: true,
});
}
39 changes: 26 additions & 13 deletions x-pack/plugins/fleet/server/services/package_policy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,13 @@ class PackagePolicyService {
soClient: SavedObjectsClientContract,
esClient: ElasticsearchClient,
packagePolicy: NewPackagePolicy,
options?: { id?: string; user?: AuthenticatedUser; bumpRevision?: boolean; force?: boolean }
options?: {
id?: string;
user?: AuthenticatedUser;
bumpRevision?: boolean;
force?: boolean;
skipEnsureInstalled?: boolean;
}
): Promise<PackagePolicy> {
// Check that its agent policy does not have a package policy with the same name
const parentAgentPolicy = await agentPolicyService.get(soClient, packagePolicy.policy_id);
Expand Down Expand Up @@ -90,18 +96,25 @@ class PackagePolicyService {

// Make sure the associated package is installed
if (packagePolicy.package?.name) {
const [, pkgInfo] = await Promise.all([
ensureInstalledPackage({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
esClient,
}),
getPackageInfo({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
pkgVersion: packagePolicy.package.version,
}),
]);
const pkgInfoPromise = getPackageInfo({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
pkgVersion: packagePolicy.package.version,
});

let pkgInfo;
if (options?.skipEnsureInstalled) pkgInfo = await pkgInfoPromise;
else {
const [, packageInfo] = await Promise.all([
ensureInstalledPackage({
savedObjectsClient: soClient,
pkgName: packagePolicy.package.name,
esClient,
}),
pkgInfoPromise,
]);
pkgInfo = packageInfo;
}

// Check if it is a limited package, and if so, check that the corresponding agent policy does not
// already contain a package policy for this package
Expand Down
49 changes: 26 additions & 23 deletions x-pack/plugins/fleet/server/services/preconfiguration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import { elasticsearchServiceMock, savedObjectsClientMock } from 'src/core/serve
import type { PreconfiguredAgentPolicy } from '../../common/types';
import type { AgentPolicy, NewPackagePolicy, Output } from '../types';

import { AGENT_POLICY_SAVED_OBJECT_TYPE } from '../constants';

import { ensurePreconfiguredPackagesAndPolicies } from './preconfiguration';

const mockInstalledPackages = new Map();
Expand All @@ -27,30 +29,31 @@ const mockDefaultOutput: Output = {
function getPutPreconfiguredPackagesMock() {
const soClient = savedObjectsClientMock.create();
soClient.find.mockImplementation(async ({ type, search }) => {
const attributes = mockConfiguredPolicies.get(search!.replace(/"/g, ''));
if (attributes) {
return {
saved_objects: [
{
id: `mocked-${attributes.preconfiguration_id}`,
attributes,
type: type as string,
score: 1,
references: [],
},
],
total: 1,
page: 1,
per_page: 1,
};
} else {
return {
saved_objects: [],
total: 0,
page: 1,
per_page: 0,
};
if (type === AGENT_POLICY_SAVED_OBJECT_TYPE) {
const attributes = mockConfiguredPolicies.get(search!.replace(/"/g, ''));
if (attributes) {
return {
saved_objects: [
{
id: `mocked-${attributes.preconfiguration_id}`,
attributes,
type: type as string,
score: 1,
references: [],
},
],
total: 1,
page: 1,
per_page: 1,
};
}
}
return {
saved_objects: [],
total: 0,
page: 1,
per_page: 0,
};
});
soClient.create.mockImplementation(async (type, policy) => {
const attributes = policy as AgentPolicy;
Expand Down
Loading