Skip to content

Commit

Permalink
[Fleet] Create a fleet_enroll user and role during fleet setup (elast…
Browse files Browse the repository at this point in the history
  • Loading branch information
nchaulet committed Mar 20, 2020
1 parent 88a2971 commit 8495531
Show file tree
Hide file tree
Showing 13 changed files with 194 additions and 84 deletions.
4 changes: 2 additions & 2 deletions x-pack/plugins/ingest_manager/common/types/models/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, any>;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ classDiagram
ca_sha256
config
// Encrypted - user to create API keys
admin_username
admin_password
fleet_enroll_username
fleet_enroll_password
}

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
4 changes: 2 additions & 2 deletions x-pack/plugins/ingest_manager/server/saved_objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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' },
},
},
Expand Down
6 changes: 3 additions & 3 deletions x-pack/plugins/ingest_manager/server/services/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,13 +60,13 @@ class OutputService {
.getEncryptedSavedObjects()
?.getDecryptedAsInternalUser<Output>(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,
};
}

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_ENROLL_USERNAME = 'fleet_enroll';
const FLEET_ENROLL_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_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,
Expand Down
4 changes: 2 additions & 2 deletions x-pack/plugins/ingest_manager/server/types/models/output.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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())),
};

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'));
});
}
Loading

0 comments on commit 8495531

Please sign in to comment.