Skip to content

Commit

Permalink
[Fleet] Create a fleet_enroll user and role during fleet setup
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet committed Mar 20, 2020
1 parent 87e07ef commit 0d82d42
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>;
}> = ({ refresh }) => {
const [isFormLoading, setIsFormLoading] = useState<boolean>(false);
const core = useCore();
const usernameInput = useInput();
const passwordInput = useInput();

const onSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
Expand All @@ -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) {
Expand All @@ -48,33 +41,47 @@ export const SetupPage: React.FunctionComponent<{
};

return (
<EuiPageBody>
<EuiPageContent>
<EuiTitle>
<h1>Setup</h1>
</EuiTitle>
<EuiSpacer size="l" />
<EuiCallOut title="Warning!" color="warning" iconType="help">
<EuiText>
To setup fleet and ingest you need to a enable a user that can create API Keys and write
to logs-* and metrics-*
<WithoutHeaderLayout>
<EuiPageBody restrictWidth={528}>
<EuiPageContent
verticalPosition="center"
horizontalPosition="center"
className="eui-textCenter"
paddingSize="l"
>
<EuiSpacer size="m" />
<EuiIcon type="lock" color="subdued" size="xl" />
<EuiSpacer size="m" />
<EuiTitle size="l">
<h2>
<FormattedMessage
id="xpack.ingestManager.setupPage.title"
defaultMessage="Enable Fleet"
/>
</h2>
</EuiTitle>
<EuiSpacer size="xl" />
<EuiText color="subdued">
<FormattedMessage
id="xpack.ingestManager.setupPage.description"
defaultMessage="In order to use Fleet, you must create an Elastic user. This user can create API keys
and write to logs-* and metrics-*."
/>
</EuiText>
</EuiCallOut>
<EuiSpacer size="l" />
<EuiForm>
<form onSubmit={onSubmit}>
<EuiFormRow label="Username">
<EuiFieldText name="username" {...usernameInput.props} />
</EuiFormRow>
<EuiFormRow label="Password">
<EuiFieldPassword name="password" {...passwordInput.props} />
</EuiFormRow>
<EuiButton isLoading={isFormLoading} type="submit">
Submit
</EuiButton>
</form>
</EuiForm>
</EuiPageContent>
</EuiPageBody>
<EuiSpacer size="l" />
<EuiForm>
<form onSubmit={onSubmit}>
<EuiButton fill isLoading={isFormLoading} type="submit">
<FormattedMessage
id="xpack.ingestManager.setupPage.enableFleet"
defaultMessage="Create user and enable Fleet"
/>
</EuiButton>
</form>
</EuiForm>
<EuiSpacer size="m" />
</EuiPageContent>
</EuiPageBody>
</WithoutHeaderLayout>
);
};
29 changes: 9 additions & 20 deletions x-pack/plugins/ingest_manager/server/routes/setup/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,21 +30,12 @@ export const getFleetSetupHandler: RequestHandler = async (context, request, res
}
};

export const createFleetSetupHandler: RequestHandler<
undefined,
undefined,
TypeOf<typeof CreateFleetSetupRequestSchema.body>
> = 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 },
Expand All @@ -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 },
});
Expand Down
54 changes: 53 additions & 1 deletion x-pack/plugins/ingest_manager/server/services/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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_ADMIN_USERNAME = 'fleet_enroll';
const FLEET_ADMIN_ROLE = 'fleet_enroll';

export async function setupIngestManager(
soClient: SavedObjectsClientContract,
callCluster: CallESAsCurrentUser
) {
Expand Down Expand Up @@ -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_ADMIN_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_ADMIN_USERNAME}`,
body: {
password,
roles: [FLEET_ADMIN_ROLE],
},
});

// save fleet admin user
await outputService.updateOutput(soClient, await outputService.getDefaultOutputId(soClient), {
admin_username: FLEET_ADMIN_USERNAME,
admin_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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 1 addition & 5 deletions x-pack/test/api_integration/apis/fleet/agents/services.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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');
});
}
1 change: 1 addition & 0 deletions x-pack/test/api_integration/apis/fleet/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
}
71 changes: 71 additions & 0 deletions x-pack/test/api_integration/apis/fleet/setup.ts
Original file line number Diff line number Diff line change
@@ -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 },
});
});
});
}

0 comments on commit 0d82d42

Please sign in to comment.