Skip to content

Commit

Permalink
[BeatsCM] fix API for tokens to support any number (elastic#30335)
Browse files Browse the repository at this point in the history
* add basic script for standing up a fake env

* tweaks

* API needs to support any number of tokens not us

* [BeatsCM] Add testing script used to create test deployments

* Move to JWT for enrollment

* wrap dont scroll command

* Dont use token as token ID

* fix tests

* not sure why this file is enabled in this branch/PR…
  • Loading branch information
mattapperson committed Feb 21, 2019
1 parent f91336c commit bb8a884
Show file tree
Hide file tree
Showing 19 changed files with 466 additions and 48 deletions.
45 changes: 29 additions & 16 deletions x-pack/plugins/beats_management/public/components/enroll_beats.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,16 @@
*/
import {
EuiBasicTable,
EuiButton,
EuiCodeBlock,
EuiCopy,
EuiFlexGroup,
EuiFlexItem,
EuiLoadingSpinner,
EuiModalBody,
// @ts-ignore
EuiSelect,
EuiSpacer,
EuiTitle,
} from '@elastic/eui';
import { FormattedMessage } from '@kbn/i18n/react';
Expand Down Expand Up @@ -87,6 +91,11 @@ export class EnrollBeat extends React.Component<ComponentProps, ComponentState>
if (this.props.enrollmentToken && !this.state.enrolledBeat) {
this.waitForTokenToEnrollBeat();
}
const cmdText = `${this.state.command
.replace('{{beatType}}', this.state.beatType)
.replace('{{beatTypeInCaps}}', capitalize(this.state.beatType))} enroll ${
window.location.protocol
}//${window.location.host}${this.props.frameworkBasePath} ${this.props.enrollmentToken}`;

return (
<React.Fragment>
Expand Down Expand Up @@ -166,7 +175,7 @@ export class EnrollBeat extends React.Component<ComponentProps, ComponentState>
{this.state.command && (
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" alignItems="center">
<EuiFlexGroup justifyContent="spaceBetween" alignItems="flexEnd">
<EuiFlexItem grow={false}>
<EuiTitle size="xs">
<h3>
Expand All @@ -180,23 +189,27 @@ export class EnrollBeat extends React.Component<ComponentProps, ComponentState>
</h3>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem className="homTutorial__instruction" grow={false}>
<EuiCopy textToCopy={cmdText}>
{(copy: any) => (
<EuiButton size="s" onClick={copy}>
<FormattedMessage
id="xpack.beatsManagement.enrollBeat.copyButtonLabel"
defaultMessage="Copy command"
/>
</EuiButton>
)}
</EuiCopy>
</EuiFlexItem>
</EuiFlexGroup>
<div className="euiFormControlLayout euiFormControlLayout--fullWidth">
<div
className="euiFieldText euiFieldText--fullWidth"
style={{ textAlign: 'left' }}
>
{`$ ${this.state.command
.replace('{{beatType}}', this.state.beatType)
.replace('{{beatTypeInCaps}}', capitalize(this.state.beatType))} enroll ${
window.location.protocol
}//${window.location.host}${this.props.frameworkBasePath} ${
this.props.enrollmentToken
}`}
</div>

<div className="eui-textBreakAll">
<EuiSpacer size="m" />
<EuiCodeBlock language="sh">{`$ ${cmdText}`}</EuiCodeBlock>
</div>
<br />
<br />

<EuiSpacer size="m" />

<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup gutterSize="s" alignItems="center">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
/*
* 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 * as React from 'react';
import { FrameworkAdapter, FrameworkInfo, FrameworkUser } from './adapter_types';

export class TestingFrameworkAdapter implements FrameworkAdapter {
public get info() {
if (this.xpackInfo) {
return this.xpackInfo;
} else {
throw new Error('framework adapter must have init called before anything else');
}
}

public get currentUser() {
return this.shieldUser!;
}
private settings: any;
constructor(
private readonly xpackInfo: FrameworkInfo | null,
private readonly shieldUser: FrameworkUser | null,
public readonly version: string
) {}

// We dont really want to have this, but it's needed to conditionaly render for k7 due to
// when that data is needed.
public getUISetting(key: 'k7design'): boolean {
return this.settings[key];
}

public setUISettings = (key: string, value: any) => {
this.settings[key] = value;
};

public async waitUntilFrameworkReady(): Promise<void> {
return;
}

public renderUIAtPath(
path: string,
component: React.ReactElement<any>,
toController: 'management' | 'self' = 'self'
) {
throw new Error('not yet implamented');
}

public registerManagementSection(settings: {
id?: string;
name: string;
iconName: string;
order?: number;
}) {
throw new Error('not yet implamented');
}

public registerManagementUI(settings: {
sectionId?: string;
name: string;
basePath: string;
visable?: boolean;
order?: number;
}) {
throw new Error('not yet implamented');
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* 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 axios, { AxiosInstance } from 'axios';
import fs from 'fs';
import { join, resolve } from 'path';
import { FlatObject } from '../../../frontend_types';
import { RestAPIAdapter } from './adapter_types';
const pkg = JSON.parse(
fs.readFileSync(resolve(join(__dirname, '../../../../../../../package.json'))).toString()
);

let globalAPI: AxiosInstance;

export class NodeAxiosAPIAdapter implements RestAPIAdapter {
constructor(
private readonly username: string,
private readonly password: string,
private readonly basePath: string
) {}

public async get<ResponseData>(url: string, query?: FlatObject<object>): Promise<ResponseData> {
return await this.REST.get(url, query ? { params: query } : {}).then(resp => resp.data);
}

public async post<ResponseData>(
url: string,
body?: { [key: string]: any }
): Promise<ResponseData> {
return await this.REST.post(url, body).then(resp => resp.data);
}

public async delete<T>(url: string): Promise<T> {
return await this.REST.delete(url).then(resp => resp.data);
}

public async put<ResponseData>(url: string, body?: any): Promise<ResponseData> {
return await this.REST.put(url, body).then(resp => resp.data);
}

private get REST() {
if (globalAPI) {
return globalAPI;
}

globalAPI = axios.create({
baseURL: this.basePath,
withCredentials: true,
responseType: 'json',
timeout: 60 * 10 * 1000, // 10min
auth: {
username: this.username,
password: this.password,
},
headers: {
'Access-Control-Allow-Origin': '*',
Accept: 'application/json',
'Content-Type': 'application/json',
'kbn-version': (pkg as any).version,
'kbn-xsrf': 'xxx',
},
});
// Add a request interceptor
globalAPI.interceptors.request.use(
config => {
// Do something before request is sent
return config;
},
error => {
// Do something with request error
return Promise.reject(error);
}
);

// Add a response interceptor
globalAPI.interceptors.response.use(
response => {
// Do something with response data
return response;
},
error => {
// Do something with response error
return Promise.reject(JSON.stringify(error.response.data));
}
);

return globalAPI;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
*/

export interface CMTokensAdapter {
createEnrollmentToken(): Promise<string>;
createEnrollmentTokens(numTokens?: number): Promise<string[]>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import { CMTokensAdapter } from './adapter_types';

export class MemoryTokensAdapter implements CMTokensAdapter {
public async createEnrollmentToken(): Promise<string> {
return '2jnwkrhkwuehriauhweair';
public async createEnrollmentTokens(): Promise<string[]> {
return ['2jnwkrhkwuehriauhweair'];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ import { CMTokensAdapter } from './adapter_types';
export class RestTokensAdapter implements CMTokensAdapter {
constructor(private readonly REST: RestAPIAdapter) {}

public async createEnrollmentToken(): Promise<string> {
const tokens = (await this.REST.post<{ tokens: string[] }>('/api/beats/enrollment_tokens'))
.tokens;
return tokens[0];
public async createEnrollmentTokens(numTokens: number = 1): Promise<string[]> {
const tokens = (await this.REST.post<{ tokens: string[] }>('/api/beats/enrollment_tokens', {
num_tokens: numTokens,
})).tokens;
return tokens;
}
}
74 changes: 74 additions & 0 deletions x-pack/plugins/beats_management/public/lib/compose/scripts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* 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 { configBlockSchemas } from '../../../common/config_schemas';
import { translateConfigSchema } from '../../../common/config_schemas_translations_map';
import { RestBeatsAdapter } from '../adapters/beats/rest_beats_adapter';
import { RestConfigBlocksAdapter } from '../adapters/configuration_blocks/rest_config_blocks_adapter';
import { MemoryElasticsearchAdapter } from '../adapters/elasticsearch/memory';
import { TestingFrameworkAdapter } from '../adapters/framework/testing_framework_adapter';
import { NodeAxiosAPIAdapter } from '../adapters/rest_api/node_axios_api_adapter';
import { RestTagsAdapter } from '../adapters/tags/rest_tags_adapter';
import { RestTokensAdapter } from '../adapters/tokens/rest_tokens_adapter';
import { BeatsLib } from '../beats';
import { ConfigBlocksLib } from '../configuration_blocks';
import { ElasticsearchLib } from '../elasticsearch';
import { FrameworkLib } from '../framework';
import { TagsLib } from '../tags';
import { FrontendLibs } from '../types';

export function compose(basePath: string): FrontendLibs {
const api = new NodeAxiosAPIAdapter('elastic', 'changeme', basePath);
const esAdapter = new MemoryElasticsearchAdapter(() => true, () => '', []);
const elasticsearchLib = new ElasticsearchLib(esAdapter);
const configBlocks = new ConfigBlocksLib(
new RestConfigBlocksAdapter(api),
translateConfigSchema(configBlockSchemas)
);
const tags = new TagsLib(new RestTagsAdapter(api), elasticsearchLib);
const tokens = new RestTokensAdapter(api);
const beats = new BeatsLib(new RestBeatsAdapter(api), elasticsearchLib);

const framework = new FrameworkLib(
new TestingFrameworkAdapter(
{
basePath,
license: {
type: 'gold',
expired: false,
expiry_date_in_millis: 34353453452345,
},
security: {
enabled: true,
available: true,
},
settings: {
encryptionKey: 'xpack_beats_default_encryptionKey',
enrollmentTokensTtlInSeconds: 10 * 60, // 10 minutes
defaultUserRoles: ['superuser'],
},
},
{
username: 'joeuser',
roles: ['beats_admin'],
enabled: true,
full_name: null,
email: null,
},
'6.7.0'
)
);

const libs: FrontendLibs = {
framework,
elasticsearch: elasticsearchLib,
tags,
tokens,
beats,
configBlocks,
};
return libs;
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,9 +127,9 @@ class BeatsPageComponent extends React.PureComponent<PageProps, PageState> {
enrollmentToken={this.props.urlState.enrollmentToken}
getBeatWithToken={this.props.containers.beats.getBeatWithToken}
createEnrollmentToken={async () => {
const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken();
const enrollmentTokens = await this.props.libs.tokens.createEnrollmentTokens();
this.props.setUrlState({
enrollmentToken,
enrollmentToken: enrollmentTokens[0],
});
}}
onBeatEnrolled={() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@ export class BeatsInitialEnrollmentPage extends Component<AppPageProps, Componen
};

public createEnrollmentToken = async () => {
const enrollmentToken = await this.props.libs.tokens.createEnrollmentToken();
const enrollmentToken = await this.props.libs.tokens.createEnrollmentTokens();
this.props.setUrlState({
enrollmentToken,
enrollmentToken: enrollmentToken[0],
});
};

Expand Down
10 changes: 9 additions & 1 deletion x-pack/plugins/beats_management/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,19 @@ node scripts/jest.js plugins/beats --watch
and for functional... (from x-pack root)

```
node scripts/functional_tests --config test/api_integration/config
node scripts/functional_tests --config test/api_integration/config
```

### Run command to fake an enrolling beat (from beats_management dir)

```
node scripts/enroll.js <enrollment token>
```

### Run a command to setup a fake large-scale deployment

Note: ts-node is required to be installed gloably from NPM/Yarn for this action

```
ts-node scripts/fake_env.ts <KIBANA BASE PATH> <# of beats> <# of tags per beat> <# of congifs per tag>
```
Loading

0 comments on commit bb8a884

Please sign in to comment.