diff --git a/x-pack/plugins/ingest_manager/common/types/models/output.ts b/x-pack/plugins/ingest_manager/common/types/models/output.ts index cedf5e81f3cb6..f4ded50d7d474 100644 --- a/x-pack/plugins/ingest_manager/common/types/models/output.ts +++ b/x-pack/plugins/ingest_manager/common/types/models/output.ts @@ -15,8 +15,8 @@ export interface NewOutput { hosts?: string[]; ca_sha256?: string; api_key?: string; - admin_username?: string; - admin_password?: string; + fleet_enroll_username?: string; + fleet_enroll_password?: string; config?: Record; } diff --git a/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts b/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts index 2eda4f187dafa..dc1d748a8743a 100644 --- a/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts +++ b/x-pack/plugins/ingest_manager/common/types/rest_spec/fleet_setup.ts @@ -9,8 +9,8 @@ export interface GetFleetSetupRequest {} export interface CreateFleetSetupRequest { body: { - admin_username: string; - admin_password: string; + fleet_enroll_username: string; + fleet_enroll_password: string; }; } diff --git a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml b/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml index bb6dbc1037201..373bd10ad6628 100644 --- a/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml +++ b/x-pack/plugins/ingest_manager/dev_docs/schema/saved_objects.mml @@ -76,7 +76,7 @@ classDiagram ca_sha256 config // Encrypted - user to create API keys - admin_username - admin_password + fleet_enroll_username + fleet_enroll_password } diff --git a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx index 31e5e99ad284b..96d4d01d67a49 100644 --- a/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx +++ b/x-pack/plugins/ingest_manager/public/applications/ingest_manager/sections/fleet/setup_page/index.tsx @@ -4,29 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; import { EuiPageBody, EuiPageContent, EuiForm, - EuiFormRow, - EuiFieldText, - EuiFieldPassword, EuiText, EuiButton, - EuiCallOut, EuiTitle, EuiSpacer, + EuiIcon, } from '@elastic/eui'; -import { sendRequest, useInput, useCore } from '../../../hooks'; +import { sendRequest, useCore } from '../../../hooks'; import { fleetSetupRouteService } from '../../../services'; +import { WithoutHeaderLayout } from '../../../layouts'; export const SetupPage: React.FunctionComponent<{ refresh: () => Promise; }> = ({ refresh }) => { const [isFormLoading, setIsFormLoading] = useState(false); const core = useCore(); - const usernameInput = useInput(); - const passwordInput = useInput(); const onSubmit = async (e: React.FormEvent) => { e.preventDefault(); @@ -35,10 +32,6 @@ export const SetupPage: React.FunctionComponent<{ await sendRequest({ method: 'post', path: fleetSetupRouteService.postFleetSetupPath(), - body: JSON.stringify({ - admin_username: usernameInput.value, - admin_password: passwordInput.value, - }), }); await refresh(); } catch (error) { @@ -48,33 +41,47 @@ export const SetupPage: React.FunctionComponent<{ }; return ( - - - -

Setup

-
- - - - To setup fleet and ingest you need to a enable a user that can create API Keys and write - to logs-* and metrics-* + + + + + + + +

+ +

+
+ + + -
- - -
- - - - - - - - Submit - -
-
-
-
+ + +
+ + + +
+
+ + + + ); }; diff --git a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts b/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts index 38188bc76f5f4..5c66f9008e2a3 100644 --- a/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts +++ b/x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts @@ -3,12 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { TypeOf } from '@kbn/config-schema'; import { RequestHandler } from 'src/core/server'; -import { outputService, agentConfigService } from '../../services'; -import { CreateFleetSetupRequestSchema, CreateFleetSetupResponse } from '../../types'; -import { setup } from '../../services/setup'; -import { generateEnrollmentAPIKey } from '../../services/api_keys'; +import { outputService } from '../../services'; +import { CreateFleetSetupResponse } from '../../types'; +import { setupIngestManager, setupFleet } from '../../services/setup'; export const getFleetSetupHandler: RequestHandler = async (context, request, response) => { const soClient = context.core.savedObjects.client; @@ -32,21 +30,12 @@ export const getFleetSetupHandler: RequestHandler = async (context, request, res } }; -export const createFleetSetupHandler: RequestHandler< - undefined, - undefined, - TypeOf -> = async (context, request, response) => { - const soClient = context.core.savedObjects.client; +export const createFleetSetupHandler: RequestHandler = async (context, request, response) => { try { - await outputService.updateOutput(soClient, await outputService.getDefaultOutputId(soClient), { - admin_username: request.body.admin_username, - admin_password: request.body.admin_password, - }); - await generateEnrollmentAPIKey(soClient, { - name: 'Default', - configId: await agentConfigService.getDefaultAgentConfigId(soClient), - }); + const soClient = context.core.savedObjects.client; + const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; + await setupIngestManager(soClient, callCluster); + await setupFleet(soClient, callCluster); return response.ok({ body: { isInitialized: true }, @@ -63,7 +52,7 @@ export const ingestManagerSetupHandler: RequestHandler = async (context, request const soClient = context.core.savedObjects.client; const callCluster = context.core.elasticsearch.adminClient.callAsCurrentUser; try { - await setup(soClient, callCluster); + await setupIngestManager(soClient, callCluster); return response.ok({ body: { isInitialized: true }, }); diff --git a/x-pack/plugins/ingest_manager/server/saved_objects.ts b/x-pack/plugins/ingest_manager/server/saved_objects.ts index 31cf173c3e4f9..b1a9d14ed6e86 100644 --- a/x-pack/plugins/ingest_manager/server/saved_objects.ts +++ b/x-pack/plugins/ingest_manager/server/saved_objects.ts @@ -102,8 +102,8 @@ export const savedObjectMappings = { ca_sha256: { type: 'keyword' }, // FIXME_INGEST https://github.com/elastic/kibana/issues/56554 api_key: { type: 'keyword' }, - admin_username: { type: 'binary' }, - admin_password: { type: 'binary' }, + fleet_enroll_username: { type: 'binary' }, + fleet_enroll_password: { type: 'binary' }, config: { type: 'flattened' }, }, }, diff --git a/x-pack/plugins/ingest_manager/server/services/output.ts b/x-pack/plugins/ingest_manager/server/services/output.ts index 8503bbb56ee84..aebb8188db0cc 100644 --- a/x-pack/plugins/ingest_manager/server/services/output.ts +++ b/x-pack/plugins/ingest_manager/server/services/output.ts @@ -60,13 +60,13 @@ class OutputService { .getEncryptedSavedObjects() ?.getDecryptedAsInternalUser(OUTPUT_SAVED_OBJECT_TYPE, defaultOutputId); - if (!so || !so.attributes.admin_username || !so.attributes.admin_password) { + if (!so || !so.attributes.fleet_enroll_username || !so.attributes.fleet_enroll_password) { return null; } return { - username: so!.attributes.admin_username, - password: so!.attributes.admin_password, + username: so!.attributes.fleet_enroll_username, + password: so!.attributes.fleet_enroll_password, }; } diff --git a/x-pack/plugins/ingest_manager/server/services/setup.ts b/x-pack/plugins/ingest_manager/server/services/setup.ts index 7f72cdb88463f..7311c46320f40 100644 --- a/x-pack/plugins/ingest_manager/server/services/setup.ts +++ b/x-pack/plugins/ingest_manager/server/services/setup.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import uuid from 'uuid'; import { SavedObjectsClientContract } from 'src/core/server'; import { CallESAsCurrentUser } from '../types'; import { agentConfigService } from './agent_config'; @@ -19,8 +20,12 @@ import { } from '../../common'; import { getPackageInfo } from './epm/packages'; import { datasourceService } from './datasource'; +import { generateEnrollmentAPIKey } from './api_keys'; -export async function setup( +const FLEET_ENROLL_USERNAME = 'fleet_enroll'; +const FLEET_ENROLL_ROLE = 'fleet_enroll'; + +export async function setupIngestManager( soClient: SavedObjectsClientContract, callCluster: CallESAsCurrentUser ) { @@ -60,6 +65,53 @@ export async function setup( } } +export async function setupFleet( + soClient: SavedObjectsClientContract, + callCluster: CallESAsCurrentUser +) { + // Create fleet_enroll role + // This should be done directly in ES at some point + await callCluster('transport.request', { + method: 'PUT', + path: `/_security/role/${FLEET_ENROLL_ROLE}`, + body: { + cluster: ['monitor', 'manage_api_key'], + indices: [ + { + names: ['logs-*', 'metrics-*', 'events-*'], + privileges: ['write', 'create_index'], + }, + ], + }, + }); + const password = generateRandomPassword(); + // Create fleet enroll user + await callCluster('transport.request', { + method: 'PUT', + path: `/_security/user/${FLEET_ENROLL_USERNAME}`, + body: { + password, + roles: [FLEET_ENROLL_ROLE], + }, + }); + + // save fleet admin user + await outputService.updateOutput(soClient, await outputService.getDefaultOutputId(soClient), { + fleet_enroll_username: FLEET_ENROLL_USERNAME, + fleet_enroll_password: password, + }); + + // Generate default enrollment key + await generateEnrollmentAPIKey(soClient, { + name: 'Default', + configId: await agentConfigService.getDefaultAgentConfigId(soClient), + }); +} + +function generateRandomPassword() { + return Buffer.from(uuid.v4()).toString('base64'); +} + async function addPackageToConfig( soClient: SavedObjectsClientContract, packageToInstall: Installation, diff --git a/x-pack/plugins/ingest_manager/server/types/models/output.ts b/x-pack/plugins/ingest_manager/server/types/models/output.ts index 8c8f4c76af7fe..36b945db2cbce 100644 --- a/x-pack/plugins/ingest_manager/server/types/models/output.ts +++ b/x-pack/plugins/ingest_manager/server/types/models/output.ts @@ -15,8 +15,8 @@ const OutputBaseSchema = { type: schema.oneOf([schema.literal(OutputType.Elasticsearch)]), hosts: schema.maybe(schema.arrayOf(schema.string())), api_key: schema.maybe(schema.string()), - admin_username: schema.maybe(schema.string()), - admin_password: schema.maybe(schema.string()), + fleet_enroll_username: schema.maybe(schema.string()), + fleet_enroll_password: schema.maybe(schema.string()), config: schema.maybe(schema.recordOf(schema.string(), schema.any())), }; diff --git a/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts b/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts index 3772e6c24c56e..2244bcd44043f 100644 --- a/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts +++ b/x-pack/plugins/ingest_manager/server/types/rest_spec/fleet_setup.ts @@ -3,16 +3,10 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; export const GetFleetSetupRequestSchema = {}; -export const CreateFleetSetupRequestSchema = { - body: schema.object({ - admin_username: schema.string(), - admin_password: schema.string(), - }), -}; +export const CreateFleetSetupRequestSchema = {}; export interface CreateFleetSetupResponse { isInitialized: boolean; diff --git a/x-pack/test/api_integration/apis/fleet/agents/services.ts b/x-pack/test/api_integration/apis/fleet/agents/services.ts index 9946135568e36..8b58c381eaf02 100644 --- a/x-pack/test/api_integration/apis/fleet/agents/services.ts +++ b/x-pack/test/api_integration/apis/fleet/agents/services.ts @@ -39,10 +39,6 @@ export function setupIngest({ getService }: FtrProviderContext) { .send(); await getService('supertest') .post(`/api/ingest_manager/fleet/setup`) - .set('kbn-xsrf', 'xxx') - .send({ - admin_username: 'elastic', - admin_password: 'changeme', - }); + .set('kbn-xsrf', 'xxx'); }); } diff --git a/x-pack/test/api_integration/apis/fleet/index.js b/x-pack/test/api_integration/apis/fleet/index.js index 547bbb8c7c6ee..289af867a10f8 100644 --- a/x-pack/test/api_integration/apis/fleet/index.js +++ b/x-pack/test/api_integration/apis/fleet/index.js @@ -16,5 +16,6 @@ export default function loadTests({ loadTestFile }) { loadTestFile(require.resolve('./enrollment_api_keys/crud')); loadTestFile(require.resolve('./install')); loadTestFile(require.resolve('./agents/actions')); + loadTestFile(require.resolve('./setup')); }); } diff --git a/x-pack/test/api_integration/apis/fleet/setup.ts b/x-pack/test/api_integration/apis/fleet/setup.ts new file mode 100644 index 0000000000000..2284d067331b3 --- /dev/null +++ b/x-pack/test/api_integration/apis/fleet/setup.ts @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + const es = getService('es'); + + describe('fleet_setup', () => { + before(async () => { + try { + await es.security.deleteUser({ + username: 'fleet_enroll', + }); + } catch (e) { + if (e.meta?.statusCode !== 404) { + throw e; + } + } + try { + await es.security.deleteRole({ + name: 'fleet_enroll', + }); + } catch (e) { + if (e.meta?.statusCode !== 404) { + throw e; + } + } + }); + + it('should create a fleet_enroll user and role', async () => { + const { body: apiResponse } = await supertest + .post(`/api/ingest_manager/fleet/setup`) + .set('kbn-xsrf', 'xxxx') + .expect(200); + + expect(apiResponse.isInitialized).to.be(true); + + const { body: userResponse } = await es.security.getUser({ + username: 'fleet_enroll', + }); + + expect(userResponse).to.have.key('fleet_enroll'); + expect(userResponse.fleet_enroll.roles).to.eql(['fleet_enroll']); + + const { body: roleResponse } = await es.security.getRole({ + name: 'fleet_enroll', + }); + expect(roleResponse).to.have.key('fleet_enroll'); + expect(roleResponse.fleet_enroll).to.eql({ + cluster: ['monitor', 'manage_api_key'], + indices: [ + { + names: ['logs-*', 'metrics-*', 'events-*'], + privileges: ['write', 'create_index'], + allow_restricted_indices: false, + }, + ], + applications: [], + run_as: [], + metadata: {}, + transient_metadata: { enabled: true }, + }); + }); + }); +}