mirror of
https://github.com/elastic/kibana.git
synced 2025-04-23 17:28:26 -04:00
* [BeatsCM] fix API for tokens to support any number (#30335) * 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… # Conflicts: # x-pack/plugins/beats_management/server/lib/adapters/framework/integration_tests/kibana.ts * remove dev only k7Design
This commit is contained in:
parent
495b952235
commit
47d838e436
21 changed files with 468 additions and 91 deletions
|
@ -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';
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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">
|
||||
|
|
|
@ -32,12 +32,11 @@ export interface FrameworkAdapter {
|
|||
order?: number;
|
||||
}): void;
|
||||
setUISettings(key: string, value: any): void;
|
||||
getUISetting(key: 'k7design'): boolean;
|
||||
getUISetting(key: string): boolean;
|
||||
}
|
||||
|
||||
export const RuntimeFrameworkInfo = t.type({
|
||||
basePath: t.string,
|
||||
k7Design: t.boolean,
|
||||
license: t.type({
|
||||
type: t.union(LICENSES.map(s => t.literal(s))),
|
||||
expired: t.boolean,
|
||||
|
|
|
@ -49,19 +49,11 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
|
|||
public readonly version: string
|
||||
) {
|
||||
this.adapterService = new KibanaAdapterServiceProvider();
|
||||
|
||||
this.settingSubscription = uiSettings.getUpdate$().subscribe({
|
||||
next: ({ key, newValue }: { key: string; newValue: boolean }) => {
|
||||
if (key === 'k7design' && this.xpackInfo) {
|
||||
this.xpackInfo.k7Design = newValue;
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 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 {
|
||||
public getUISetting(key: string): boolean {
|
||||
return this.uiSettings.get(key);
|
||||
}
|
||||
|
||||
|
@ -86,7 +78,6 @@ export class KibanaFrameworkAdapter implements FrameworkAdapter {
|
|||
try {
|
||||
xpackInfoUnpacked = {
|
||||
basePath: this.getBasePath(),
|
||||
k7Design: this.uiSettings.get('k7design'),
|
||||
license: {
|
||||
type: xpackInfo ? xpackInfo.getLicense().type : 'oss',
|
||||
expired: xpackInfo ? !xpackInfo.getLicense().isActive : false,
|
||||
|
|
|
@ -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: string): 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');
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -5,5 +5,5 @@
|
|||
*/
|
||||
|
||||
export interface CMTokensAdapter {
|
||||
createEnrollmentToken(): Promise<string>;
|
||||
createEnrollmentTokens(numTokens?: number): Promise<string[]>;
|
||||
}
|
||||
|
|
|
@ -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'];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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={() => {
|
||||
|
|
|
@ -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],
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ 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)
|
||||
|
@ -20,3 +20,11 @@ and for functional... (from x-pack root)
|
|||
```
|
||||
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>
|
||||
```
|
||||
|
|
156
x-pack/plugins/beats_management/scripts/fake_env.ts
Normal file
156
x-pack/plugins/beats_management/scripts/fake_env.ts
Normal file
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* 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 Chance from 'chance'; // eslint-disable-line
|
||||
// @ts-ignore
|
||||
import request from 'request';
|
||||
import uuidv4 from 'uuid/v4';
|
||||
import { configBlockSchemas } from 'x-pack/plugins/beats_management/common/config_schemas';
|
||||
import { BeatTag } from '../common/domain_types';
|
||||
import { compose } from '../public/lib/compose/scripts';
|
||||
const args = process.argv.slice(2);
|
||||
const chance = new Chance();
|
||||
|
||||
function sleep(ms: number) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
function getRandomColor() {
|
||||
const letters = '0123456789ABCDEF';
|
||||
let color = '#';
|
||||
for (let i = 0; i < 6; i++) {
|
||||
color += letters[Math.floor(Math.random() * 16)];
|
||||
}
|
||||
return color;
|
||||
}
|
||||
|
||||
const enroll = async (kibanaURL: string, token: string) => {
|
||||
const beatId = uuidv4();
|
||||
|
||||
await request(
|
||||
{
|
||||
url: `${kibanaURL}/api/beats/agent/${beatId}`,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'kbn-xsrf': 'xxx',
|
||||
'kbn-beats-enrollment-token': token,
|
||||
},
|
||||
body: JSON.stringify({
|
||||
type: Math.random() >= 0.5 ? 'filebeat' : 'metricbeat',
|
||||
host_name: `${chance.word()}.local`,
|
||||
name: chance.word(),
|
||||
version: '6.7.0',
|
||||
}),
|
||||
},
|
||||
(error: any, response: any, body: any) => {
|
||||
const res = JSON.parse(body);
|
||||
if (res.message) {
|
||||
// tslint:disable-next-line
|
||||
console.log(res.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const start = async (
|
||||
kibanaURL: string,
|
||||
numberOfBeats = 10,
|
||||
maxNumberOfTagsPerBeat = 2,
|
||||
maxNumberOfConfigsPerTag = 4
|
||||
) => {
|
||||
try {
|
||||
const libs = compose(kibanaURL);
|
||||
// tslint:disable-next-line
|
||||
console.error(`Enrolling ${numberOfBeats} fake beats...`);
|
||||
|
||||
const enrollmentTokens = await libs.tokens.createEnrollmentTokens(numberOfBeats);
|
||||
process.stdout.write(`enrolling fake beats... 0 of ${numberOfBeats}`);
|
||||
let count = 0;
|
||||
for (const token of enrollmentTokens) {
|
||||
count++;
|
||||
// @ts-ignore
|
||||
process.stdout.clearLine();
|
||||
// @ts-ignore
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(`enrolling fake beats... ${count} of ${numberOfBeats}`);
|
||||
|
||||
await enroll(kibanaURL, token);
|
||||
await sleep(10);
|
||||
}
|
||||
process.stdout.write('\n');
|
||||
|
||||
await sleep(2000);
|
||||
// tslint:disable-next-line
|
||||
console.error(`${numberOfBeats} fake beats are enrolled`);
|
||||
const beats = await libs.beats.getAll();
|
||||
|
||||
// tslint:disable-next-line
|
||||
console.error(`Creating tags, configs, and assigning them...`);
|
||||
process.stdout.write(`creating tags/configs for beat... 0 of ${numberOfBeats}`);
|
||||
count = 0;
|
||||
for (const beat of beats) {
|
||||
count++;
|
||||
// @ts-ignore
|
||||
process.stdout.clearLine();
|
||||
// @ts-ignore
|
||||
process.stdout.cursorTo(0);
|
||||
process.stdout.write(`creating tags w/configs for beat... ${count} of ${numberOfBeats}`);
|
||||
|
||||
const tags = await Promise.all(
|
||||
[...Array(maxNumberOfTagsPerBeat)].map(() => {
|
||||
return libs.tags.upsertTag({
|
||||
name: chance.word(),
|
||||
color: getRandomColor(),
|
||||
hasConfigurationBlocksTypes: [] as string[],
|
||||
} as BeatTag);
|
||||
})
|
||||
);
|
||||
await libs.beats.assignTagsToBeats(
|
||||
tags.map((tag: any) => ({
|
||||
beatId: beat.id,
|
||||
tag: tag.id,
|
||||
}))
|
||||
);
|
||||
|
||||
await Promise.all(
|
||||
tags.map((tag: any) => {
|
||||
return libs.configBlocks.upsert(
|
||||
[...Array(maxNumberOfConfigsPerTag)].map(
|
||||
() =>
|
||||
({
|
||||
type: configBlockSchemas[Math.floor(Math.random())].id,
|
||||
description: `Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sint ista Graecorum;
|
||||
Nihil ad rem! Ne sit sane; Quod quidem nobis non saepe contingit.
|
||||
Duo Reges: constructio interrete. Itaque his sapiens semper vacabit.`.substring(
|
||||
0,
|
||||
Math.floor(Math.random() * (0 - 115 + 1))
|
||||
),
|
||||
tag: tag.id,
|
||||
last_updated: new Date(),
|
||||
config: {},
|
||||
} as any)
|
||||
)
|
||||
);
|
||||
})
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (e.response && e.response.data && e.response.message) {
|
||||
// tslint:disable-next-line
|
||||
console.error(e.response.data.message);
|
||||
} else if (e.response && e.response.data && e.response.reason) {
|
||||
// tslint:disable-next-line
|
||||
console.error(e.response.data.reason);
|
||||
} else if (e.code) {
|
||||
// tslint:disable-next-line
|
||||
console.error(e.code);
|
||||
} else {
|
||||
// tslint:disable-next-line
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// @ts-ignore
|
||||
start(...args);
|
|
@ -1,31 +0,0 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
// file.skip
|
||||
|
||||
import { camelCase } from 'lodash';
|
||||
// @ts-ignore
|
||||
import * as kbnTestServer from '../../../../../../../../src/test_utils/kbn_server';
|
||||
// @ts-ignore
|
||||
import { xpackKbnServerConfig } from '../../../../../../../test_utils/kbn_server_config';
|
||||
import { PLUGIN } from './../../../../../common/constants/plugin';
|
||||
import { KibanaBackendFrameworkAdapter } from './../kibana_framework_adapter';
|
||||
import { contractTests } from './test_contract';
|
||||
|
||||
let servers: any;
|
||||
contractTests('Kibana Framework Adapter', {
|
||||
async before() {
|
||||
servers = await kbnTestServer.startTestServers({
|
||||
adjustTimeout: (t: number) => jest.setTimeout(t),
|
||||
settings: xpackKbnServerConfig,
|
||||
});
|
||||
},
|
||||
async after() {
|
||||
await servers.stop();
|
||||
},
|
||||
adapterSetup: () => {
|
||||
return new KibanaBackendFrameworkAdapter(camelCase(PLUGIN.ID), servers.kbnServer.server);
|
||||
},
|
||||
});
|
|
@ -13,7 +13,7 @@ interface ContractConfig {
|
|||
|
||||
export const contractTests = (testName: string, config: ContractConfig) => {
|
||||
describe(testName, () => {
|
||||
let frameworkAdapter: any;
|
||||
let frameworkAdapter: BackendFrameworkAdapter;
|
||||
beforeAll(config.before);
|
||||
afterAll(config.after);
|
||||
beforeEach(async () => {
|
||||
|
@ -21,9 +21,9 @@ export const contractTests = (testName: string, config: ContractConfig) => {
|
|||
});
|
||||
|
||||
it('Should have tests here', () => {
|
||||
expect(frameworkAdapter.info).toHaveProperty('server');
|
||||
|
||||
expect(frameworkAdapter).toHaveProperty('server');
|
||||
expect(frameworkAdapter.server).toHaveProperty('plugins');
|
||||
expect(frameworkAdapter.server.plugins).toHaveProperty('security');
|
||||
});
|
||||
});
|
||||
};
|
||||
|
|
|
@ -13,5 +13,5 @@ export interface TokenEnrollmentData {
|
|||
export interface CMTokensAdapter {
|
||||
deleteEnrollmentToken(user: FrameworkUser, enrollmentToken: string): Promise<void>;
|
||||
getEnrollmentToken(user: FrameworkUser, enrollmentToken: string): Promise<TokenEnrollmentData>;
|
||||
upsertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]): Promise<TokenEnrollmentData[]>;
|
||||
insertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]): Promise<TokenEnrollmentData[]>;
|
||||
}
|
||||
|
|
|
@ -35,6 +35,7 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter {
|
|||
};
|
||||
|
||||
const response = await this.database.get(user, params);
|
||||
|
||||
const tokenDetails = get<TokenEnrollmentData>(response, '_source.enrollment_token', {
|
||||
expires_on: '0',
|
||||
token: null,
|
||||
|
@ -50,7 +51,7 @@ export class ElasticsearchTokensAdapter implements CMTokensAdapter {
|
|||
);
|
||||
}
|
||||
|
||||
public async upsertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]) {
|
||||
public async insertTokens(user: FrameworkUser, tokens: TokenEnrollmentData[]) {
|
||||
const body = flatten(
|
||||
tokens.map(token => [
|
||||
{ index: { _id: `enrollment_token:${token.token}` } },
|
||||
|
|
|
@ -31,7 +31,7 @@ export class MemoryTokensAdapter implements CMTokensAdapter {
|
|||
});
|
||||
}
|
||||
|
||||
public async upsertTokens(user: FrameworkAuthenticatedUser, tokens: TokenEnrollmentData[]) {
|
||||
public async insertTokens(user: FrameworkAuthenticatedUser, tokens: TokenEnrollmentData[]) {
|
||||
tokens.forEach(token => {
|
||||
const existingIndex = this.tokenDB.findIndex(t => t.token === token.token);
|
||||
if (existingIndex !== -1) {
|
||||
|
|
|
@ -96,7 +96,6 @@ export class CMBeatsDomain {
|
|||
beat: Partial<CMBeat>
|
||||
): Promise<{ status: string; accessToken?: string }> {
|
||||
const { token, expires_on } = await this.tokens.getEnrollmentToken(enrollmentToken);
|
||||
|
||||
if (expires_on && moment(expires_on).isBefore(moment())) {
|
||||
return { status: BeatEnrollmentStatus.ExpiredEnrollmentToken };
|
||||
}
|
||||
|
|
|
@ -3,10 +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 { timingSafeEqual } from 'crypto';
|
||||
import { randomBytes, timingSafeEqual } from 'crypto';
|
||||
import { sign as signToken, verify as verifyToken } from 'jsonwebtoken';
|
||||
import { chunk } from 'lodash';
|
||||
import moment from 'moment';
|
||||
import uuid from 'uuid';
|
||||
import { FrameworkUser } from './adapters/framework/adapter_types';
|
||||
import { CMTokensAdapter } from './adapters/tokens/adapter_types';
|
||||
import { CMServerLibs } from './types';
|
||||
|
@ -28,7 +28,6 @@ export class CMTokensDomain {
|
|||
this.framework.internalUser,
|
||||
enrollmentToken
|
||||
);
|
||||
|
||||
if (!fullToken) {
|
||||
return {
|
||||
token: null,
|
||||
|
@ -103,7 +102,7 @@ export class CMTokensDomain {
|
|||
|
||||
const tokenData = {
|
||||
created: moment().toJSON(),
|
||||
randomHash: this.createRandomHash(),
|
||||
randomHash: randomBytes(26).toString(),
|
||||
};
|
||||
|
||||
return signToken(tokenData, enrollmentTokenSecret);
|
||||
|
@ -119,20 +118,25 @@ export class CMTokensDomain {
|
|||
const enrollmentTokenExpiration = moment()
|
||||
.add(enrollmentTokensTtlInSeconds, 'seconds')
|
||||
.toJSON();
|
||||
const enrollmentTokenSecret = this.framework.getSetting('encryptionKey');
|
||||
|
||||
while (tokens.length < numTokens) {
|
||||
const tokenData = {
|
||||
created: moment().toJSON(),
|
||||
expires: enrollmentTokenExpiration,
|
||||
randomHash: randomBytes(26).toString(),
|
||||
};
|
||||
|
||||
tokens.push({
|
||||
expires_on: enrollmentTokenExpiration,
|
||||
token: this.createRandomHash(),
|
||||
token: signToken(tokenData, enrollmentTokenSecret),
|
||||
});
|
||||
}
|
||||
|
||||
await this.adapter.upsertTokens(user, tokens);
|
||||
await Promise.all(
|
||||
chunk(tokens, 100).map(tokenChunk => this.adapter.insertTokens(user, tokenChunk))
|
||||
);
|
||||
|
||||
return tokens.map(token => token.token);
|
||||
}
|
||||
|
||||
private createRandomHash() {
|
||||
return uuid.v4().replace(/-/g, '');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
|
||||
export default function ({ getService }) {
|
||||
const supertest = getService('supertest');
|
||||
const chance = getService('chance');
|
||||
const es = getService('es');
|
||||
|
||||
describe('create_enrollment_token', () => {
|
||||
|
@ -42,7 +41,7 @@ export default function ({ getService }) {
|
|||
});
|
||||
|
||||
it('should create the specified number of tokens', async () => {
|
||||
const numTokens = chance.integer({ min: 1, max: 2000 });
|
||||
const numTokens = 1000;
|
||||
|
||||
const { body: apiResponse } = await supertest
|
||||
.post(
|
||||
|
@ -66,8 +65,10 @@ export default function ({ getService }) {
|
|||
const tokensInEs = esResponse.hits.hits
|
||||
.map(hit => hit._source.enrollment_token.token);
|
||||
|
||||
expect(tokensFromApi).to.be.an('array');
|
||||
expect(tokensFromApi.length).to.eql(numTokens);
|
||||
expect(tokensFromApi).to.eql(tokensInEs);
|
||||
expect(tokensInEs.length).to.eql(numTokens);
|
||||
expect(tokensFromApi.sort()).to.eql(tokensInEs.sort());
|
||||
});
|
||||
|
||||
it('should set token expiration to 10 minutes from now by default', async () => {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue