mirror of
https://github.com/elastic/kibana.git
synced 2025-04-24 09:48:58 -04:00
* [BeatsCM] Move API to new return format (#31660) * add and assign types * move error management to the lib vs each route. More DRY and easier to test * move more endpoints to the new format * fix result for not-yet-pages beats list * refine more endpoints * move more routes to new format * UI now propperly connected to the API * uodate tests and documenting testing in readme # Conflicts: # x-pack/plugins/beats_management/server/rest_api/beats/configuration.ts * fix linting errors
This commit is contained in:
parent
b5e8e9fae9
commit
7170757d49
51 changed files with 726 additions and 535 deletions
116
x-pack/plugins/beats_management/common/return_types.ts
Normal file
116
x-pack/plugins/beats_management/common/return_types.ts
Normal file
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
export interface BaseReturnType {
|
||||
error?: {
|
||||
message: string;
|
||||
code?: number;
|
||||
};
|
||||
success: boolean;
|
||||
}
|
||||
|
||||
export interface ReturnTypeCreate<T> extends BaseReturnType {
|
||||
item: T;
|
||||
action: 'created';
|
||||
}
|
||||
|
||||
export interface ReturnTypeUpdate<T> extends BaseReturnType {
|
||||
item: T;
|
||||
action: 'updated';
|
||||
}
|
||||
|
||||
export interface ReturnTypeBulkCreate<T> extends BaseReturnType {
|
||||
results: Array<{
|
||||
item: T;
|
||||
success: boolean;
|
||||
action: 'created';
|
||||
error?: {
|
||||
message: string;
|
||||
code?: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
// delete
|
||||
export interface ReturnTypeDelete extends BaseReturnType {
|
||||
action: 'deleted';
|
||||
}
|
||||
|
||||
export interface ReturnTypeBulkDelete extends BaseReturnType {
|
||||
results: Array<{
|
||||
success: boolean;
|
||||
action: 'deleted';
|
||||
error?: {
|
||||
message: string;
|
||||
code?: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
// upsert
|
||||
export interface ReturnTypeUpsert<T> extends BaseReturnType {
|
||||
item: T;
|
||||
action: 'created' | 'updated';
|
||||
}
|
||||
|
||||
// upsert bulk
|
||||
export interface ReturnTypeBulkUpsert extends BaseReturnType {
|
||||
results: Array<{
|
||||
success: boolean;
|
||||
action: 'created' | 'updated';
|
||||
error?: {
|
||||
message: string;
|
||||
code?: number;
|
||||
};
|
||||
}>;
|
||||
}
|
||||
|
||||
// list
|
||||
export interface ReturnTypeList<T> extends BaseReturnType {
|
||||
list: T[];
|
||||
page: number;
|
||||
total: number;
|
||||
}
|
||||
|
||||
// get
|
||||
export interface ReturnTypeGet<T> extends BaseReturnType {
|
||||
item: T;
|
||||
}
|
||||
|
||||
export interface ReturnTypeBulkGet<T> extends BaseReturnType {
|
||||
items: T[];
|
||||
}
|
||||
|
||||
// action -- e.g. validate config block. Like ES simulate endpoint
|
||||
export interface ReturnTypeAction extends BaseReturnType {
|
||||
result: {
|
||||
[key: string]: any;
|
||||
};
|
||||
}
|
||||
// e.g.
|
||||
// {
|
||||
// result: {
|
||||
// username: { valid: true },
|
||||
// password: { valid: false, error: 'something' },
|
||||
// hosts: [
|
||||
// { valid: false }, { valid: true },
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
|
||||
// bulk action -- e.g. assign tags to beats
|
||||
export interface ReturnTypeBulkAction extends BaseReturnType {
|
||||
results?: Array<{
|
||||
success: boolean;
|
||||
result?: {
|
||||
[key: string]: any;
|
||||
};
|
||||
error?: {
|
||||
message: string;
|
||||
code?: number;
|
||||
};
|
||||
}>;
|
||||
}
|
|
@ -15,7 +15,7 @@ import { ConfigurationBlock } from '../../common/domain_types';
|
|||
interface ComponentProps {
|
||||
configs: {
|
||||
error?: string | undefined;
|
||||
blocks: ConfigurationBlock[];
|
||||
list: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
};
|
||||
|
@ -30,7 +30,7 @@ const pagination = {
|
|||
|
||||
const ConfigListUi: React.SFC<ComponentProps> = props => (
|
||||
<EuiBasicTable
|
||||
items={props.configs.blocks || []}
|
||||
items={props.configs.list || []}
|
||||
itemId="id"
|
||||
pagination={{
|
||||
...pagination,
|
||||
|
|
|
@ -34,7 +34,7 @@ interface TagEditProps {
|
|||
tag: BeatTag;
|
||||
configuration_blocks: {
|
||||
error?: string | undefined;
|
||||
blocks: ConfigurationBlock[];
|
||||
list: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
};
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
*/
|
||||
|
||||
import { CMBeat } from '../../../../common/domain_types';
|
||||
import { ReturnTypeBulkAction } from '../../../../common/return_types';
|
||||
|
||||
export interface CMBeatsAdapter {
|
||||
get(id: string): Promise<CMBeat | null>;
|
||||
update(id: string, beatData: Partial<CMBeat>): Promise<boolean>;
|
||||
getBeatsWithTag(tagId: string): Promise<CMBeat[]>;
|
||||
getAll(ESQuery?: any): Promise<CMBeat[]>;
|
||||
removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise<BeatsRemovalReturn[]>;
|
||||
assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise<CMAssignmentReturn[]>;
|
||||
removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise<ReturnTypeBulkAction['results']>;
|
||||
assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise<ReturnTypeBulkAction['results']>;
|
||||
getBeatWithToken(enrollmentToken: string): Promise<CMBeat | null>;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,14 +5,9 @@
|
|||
*/
|
||||
|
||||
import { omit } from 'lodash';
|
||||
|
||||
import { CMBeat } from '../../../../common/domain_types';
|
||||
import {
|
||||
BeatsRemovalReturn,
|
||||
BeatsTagAssignment,
|
||||
CMAssignmentReturn,
|
||||
CMBeatsAdapter,
|
||||
} from './adapter_types';
|
||||
import { ReturnTypeBulkAction } from '../../../../common/return_types';
|
||||
import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types';
|
||||
|
||||
export class MemoryBeatsAdapter implements CMBeatsAdapter {
|
||||
private beatsDB: CMBeat[];
|
||||
|
@ -46,7 +41,9 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter {
|
|||
public async getBeatWithToken(enrollmentToken: string): Promise<CMBeat | null> {
|
||||
return this.beatsDB.map<CMBeat>((beat: any) => omit(beat, ['access_token']))[0];
|
||||
}
|
||||
public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise<BeatsRemovalReturn[]> {
|
||||
public async removeTagsFromBeats(
|
||||
removals: BeatsTagAssignment[]
|
||||
): Promise<ReturnTypeBulkAction['results']> {
|
||||
const beatIds = removals.map(r => r.beatId);
|
||||
|
||||
const response = this.beatsDB
|
||||
|
@ -76,7 +73,9 @@ export class MemoryBeatsAdapter implements CMBeatsAdapter {
|
|||
}));
|
||||
}
|
||||
|
||||
public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise<CMAssignmentReturn[]> {
|
||||
public async assignTagsToBeats(
|
||||
assignments: BeatsTagAssignment[]
|
||||
): Promise<ReturnTypeBulkAction['results']> {
|
||||
const beatIds = assignments.map(r => r.beatId);
|
||||
|
||||
this.beatsDB
|
||||
|
|
|
@ -5,19 +5,20 @@
|
|||
*/
|
||||
|
||||
import { CMBeat } from '../../../../common/domain_types';
|
||||
import { RestAPIAdapter } from '../rest_api/adapter_types';
|
||||
import {
|
||||
BeatsRemovalReturn,
|
||||
BeatsTagAssignment,
|
||||
CMAssignmentReturn,
|
||||
CMBeatsAdapter,
|
||||
} from './adapter_types';
|
||||
ReturnTypeBulkAction,
|
||||
ReturnTypeGet,
|
||||
ReturnTypeList,
|
||||
ReturnTypeUpdate,
|
||||
} from '../../../../common/return_types';
|
||||
import { RestAPIAdapter } from '../rest_api/adapter_types';
|
||||
import { BeatsTagAssignment, CMBeatsAdapter } from './adapter_types';
|
||||
export class RestBeatsAdapter implements CMBeatsAdapter {
|
||||
constructor(private readonly REST: RestAPIAdapter) {}
|
||||
|
||||
public async get(id: string): Promise<CMBeat | null> {
|
||||
try {
|
||||
return await this.REST.get<CMBeat>(`/api/beats/agent/${id}`);
|
||||
return (await this.REST.get<ReturnTypeGet<CMBeat>>(`/api/beats/agent/${id}`)).item;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -25,7 +26,9 @@ export class RestBeatsAdapter implements CMBeatsAdapter {
|
|||
|
||||
public async getBeatWithToken(enrollmentToken: string): Promise<CMBeat | null> {
|
||||
try {
|
||||
return await this.REST.get<CMBeat>(`/api/beats/agent/unknown/${enrollmentToken}`);
|
||||
return (await this.REST.get<ReturnTypeGet<CMBeat>>(
|
||||
`/api/beats/agent/unknown/${enrollmentToken}`
|
||||
)).item;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
|
@ -33,7 +36,8 @@ export class RestBeatsAdapter implements CMBeatsAdapter {
|
|||
|
||||
public async getAll(ESQuery?: string): Promise<CMBeat[]> {
|
||||
try {
|
||||
return (await this.REST.get<{ beats: CMBeat[] }>('/api/beats/agents/all', { ESQuery })).beats;
|
||||
return (await this.REST.get<ReturnTypeList<CMBeat>>('/api/beats/agents/all', { ESQuery }))
|
||||
.list;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
@ -41,32 +45,30 @@ export class RestBeatsAdapter implements CMBeatsAdapter {
|
|||
|
||||
public async getBeatsWithTag(tagId: string): Promise<CMBeat[]> {
|
||||
try {
|
||||
return (await this.REST.get<{ beats: CMBeat[] }>(`/api/beats/agents/tag/${tagId}`)).beats;
|
||||
return (await this.REST.get<ReturnTypeList<CMBeat>>(`/api/beats/agents/tag/${tagId}`)).list;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async update(id: string, beatData: Partial<CMBeat>): Promise<boolean> {
|
||||
await this.REST.put<{ success: true }>(`/api/beats/agent/${id}`, beatData);
|
||||
await this.REST.put<ReturnTypeUpdate<CMBeat>>(`/api/beats/agent/${id}`, beatData);
|
||||
return true;
|
||||
}
|
||||
|
||||
public async removeTagsFromBeats(removals: BeatsTagAssignment[]): Promise<BeatsRemovalReturn[]> {
|
||||
return (await this.REST.post<{ removals: BeatsRemovalReturn[] }>(
|
||||
`/api/beats/agents_tags/removals`,
|
||||
{
|
||||
removals,
|
||||
}
|
||||
)).removals;
|
||||
public async removeTagsFromBeats(
|
||||
removals: BeatsTagAssignment[]
|
||||
): Promise<ReturnTypeBulkAction['results']> {
|
||||
return (await this.REST.post<ReturnTypeBulkAction>(`/api/beats/agents_tags/removals`, {
|
||||
removals,
|
||||
})).results;
|
||||
}
|
||||
|
||||
public async assignTagsToBeats(assignments: BeatsTagAssignment[]): Promise<CMAssignmentReturn[]> {
|
||||
return (await this.REST.post<{ assignments: CMAssignmentReturn[] }>(
|
||||
`/api/beats/agents_tags/assignments`,
|
||||
{
|
||||
assignments,
|
||||
}
|
||||
)).assignments;
|
||||
public async assignTagsToBeats(
|
||||
assignments: BeatsTagAssignment[]
|
||||
): Promise<ReturnTypeBulkAction['results']> {
|
||||
return (await this.REST.post<ReturnTypeBulkAction>(`/api/beats/agents_tags/assignments`, {
|
||||
assignments,
|
||||
})).results;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,19 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import { ConfigurationBlock } from '../../../../common/domain_types';
|
||||
import { ReturnTypeBulkUpsert, ReturnTypeList } from '../../../../common/return_types';
|
||||
|
||||
export interface FrontendConfigBlocksAdapter {
|
||||
upsert(
|
||||
blocks: ConfigurationBlock[]
|
||||
): Promise<Array<{ success?: boolean; blockID?: string; error?: string }>>;
|
||||
getForTags(
|
||||
tagIds: string[],
|
||||
page: number
|
||||
): Promise<{
|
||||
error?: string;
|
||||
blocks: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>;
|
||||
upsert(blocks: ConfigurationBlock[]): Promise<ReturnTypeBulkUpsert>;
|
||||
getForTags(tagIds: string[], page: number): Promise<ReturnTypeList<ConfigurationBlock>>;
|
||||
delete(id: string): Promise<boolean>;
|
||||
}
|
||||
|
|
|
@ -5,30 +5,26 @@
|
|||
*/
|
||||
|
||||
import { ConfigurationBlock } from '../../../../common/domain_types';
|
||||
import { ReturnTypeBulkUpsert, ReturnTypeList } from '../../../../common/return_types';
|
||||
import { FrontendConfigBlocksAdapter } from './adapter_types';
|
||||
|
||||
export class MemoryConfigBlocksAdapter implements FrontendConfigBlocksAdapter {
|
||||
constructor(private db: ConfigurationBlock[]) {}
|
||||
|
||||
public async upsert(blocks: ConfigurationBlock[]) {
|
||||
public async upsert(blocks: ConfigurationBlock[]): Promise<ReturnTypeBulkUpsert> {
|
||||
this.db = this.db.concat(blocks);
|
||||
return blocks.map(() => ({
|
||||
success: true,
|
||||
blockID: Math.random()
|
||||
.toString(36)
|
||||
.substring(7),
|
||||
}));
|
||||
}
|
||||
public async getForTags(
|
||||
tagIds: string[]
|
||||
): Promise<{
|
||||
error?: string;
|
||||
blocks: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
}> {
|
||||
return {
|
||||
blocks: this.db.filter(block => tagIds.includes(block.tag)),
|
||||
success: true,
|
||||
results: blocks.map(() => ({
|
||||
success: true,
|
||||
action: 'created',
|
||||
})),
|
||||
} as ReturnTypeBulkUpsert;
|
||||
}
|
||||
public async getForTags(tagIds: string[]): Promise<ReturnTypeList<ConfigurationBlock>> {
|
||||
return {
|
||||
success: true,
|
||||
list: this.db.filter(block => tagIds.includes(block.tag)),
|
||||
page: 0,
|
||||
total: this.db.filter(block => tagIds.includes(block.tag)).length,
|
||||
};
|
||||
|
|
|
@ -5,6 +5,11 @@
|
|||
*/
|
||||
|
||||
import { ConfigurationBlock } from '../../../../common/domain_types';
|
||||
import {
|
||||
ReturnTypeBulkDelete,
|
||||
ReturnTypeBulkUpsert,
|
||||
ReturnTypeList,
|
||||
} from '../../../../common/return_types';
|
||||
import { RestAPIAdapter } from '../rest_api/adapter_types';
|
||||
import { FrontendConfigBlocksAdapter } from './adapter_types';
|
||||
|
||||
|
@ -12,29 +17,19 @@ export class RestConfigBlocksAdapter implements FrontendConfigBlocksAdapter {
|
|||
constructor(private readonly REST: RestAPIAdapter) {}
|
||||
|
||||
public async upsert(blocks: ConfigurationBlock[]) {
|
||||
const result = await this.REST.put<
|
||||
Array<{ success?: boolean; blockID?: string; error?: string }>
|
||||
>(`/api/beats/configurations`, blocks);
|
||||
const result = await this.REST.put<ReturnTypeBulkUpsert>(`/api/beats/configurations`, blocks);
|
||||
return result;
|
||||
}
|
||||
public async getForTags(
|
||||
tagIds: string[],
|
||||
page: number
|
||||
): Promise<{
|
||||
error?: string;
|
||||
blocks: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
}> {
|
||||
return await this.REST.get<{
|
||||
error?: string;
|
||||
blocks: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
}>(`/api/beats/configurations/${tagIds.join(',')}/${page}`);
|
||||
): Promise<ReturnTypeList<ConfigurationBlock>> {
|
||||
return await this.REST.get<ReturnTypeList<ConfigurationBlock>>(
|
||||
`/api/beats/configurations/${tagIds.join(',')}/${page}`
|
||||
);
|
||||
}
|
||||
public async delete(id: string): Promise<boolean> {
|
||||
return (await this.REST.delete<{ success: boolean }>(`/api/beats/configurations/${id}`))
|
||||
return (await this.REST.delete<ReturnTypeBulkDelete>(`/api/beats/configurations/${id}`))
|
||||
.success;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,12 @@
|
|||
|
||||
import { uniq } from 'lodash';
|
||||
import { BeatTag, CMBeat } from '../../../../common/domain_types';
|
||||
import {
|
||||
ReturnTypeBulkDelete,
|
||||
ReturnTypeBulkGet,
|
||||
ReturnTypeList,
|
||||
ReturnTypeUpsert,
|
||||
} from '../../../../common/return_types';
|
||||
import { RestAPIAdapter } from '../rest_api/adapter_types';
|
||||
import { CMTagsAdapter } from './adapter_types';
|
||||
|
||||
|
@ -14,7 +20,9 @@ export class RestTagsAdapter implements CMTagsAdapter {
|
|||
|
||||
public async getTagsWithIds(tagIds: string[]): Promise<BeatTag[]> {
|
||||
try {
|
||||
return await this.REST.get<BeatTag[]>(`/api/beats/tags/${uniq(tagIds).join(',')}`);
|
||||
return (await this.REST.get<ReturnTypeBulkGet<BeatTag>>(
|
||||
`/api/beats/tags/${uniq(tagIds).join(',')}`
|
||||
)).items;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
@ -22,20 +30,20 @@ export class RestTagsAdapter implements CMTagsAdapter {
|
|||
|
||||
public async getAll(ESQuery: string): Promise<BeatTag[]> {
|
||||
try {
|
||||
return await this.REST.get<BeatTag[]>(`/api/beats/tags`, { ESQuery });
|
||||
return (await this.REST.get<ReturnTypeList<BeatTag>>(`/api/beats/tags`, { ESQuery })).list;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
public async delete(tagIds: string[]): Promise<boolean> {
|
||||
return (await this.REST.delete<{ success: boolean }>(
|
||||
return (await this.REST.delete<ReturnTypeBulkDelete>(
|
||||
`/api/beats/tags/${uniq(tagIds).join(',')}`
|
||||
)).success;
|
||||
}
|
||||
|
||||
public async upsertTag(tag: BeatTag): Promise<BeatTag | null> {
|
||||
const response = await this.REST.put<{ success: boolean }>(`/api/beats/tag/${tag.id}`, {
|
||||
const response = await this.REST.put<ReturnTypeUpsert<BeatTag>>(`/api/beats/tag/${tag.id}`, {
|
||||
color: tag.color,
|
||||
name: tag.name,
|
||||
});
|
||||
|
@ -45,9 +53,9 @@ export class RestTagsAdapter implements CMTagsAdapter {
|
|||
|
||||
public async getAssignable(beats: CMBeat[]) {
|
||||
try {
|
||||
return await this.REST.get<BeatTag[]>(
|
||||
return (await this.REST.get<ReturnTypeBulkGet<BeatTag>>(
|
||||
`/api/beats/tags/assignable/${beats.map(beat => beat.id).join(',')}`
|
||||
);
|
||||
)).items;
|
||||
} catch (e) {
|
||||
return [];
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ReturnTypeBulkCreate } from '../../../../common/return_types';
|
||||
import { RestAPIAdapter } from '../rest_api/adapter_types';
|
||||
import { CMTokensAdapter } from './adapter_types';
|
||||
|
||||
|
@ -11,9 +12,12 @@ export class RestTokensAdapter implements CMTokensAdapter {
|
|||
constructor(private readonly REST: RestAPIAdapter) {}
|
||||
|
||||
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;
|
||||
const results = (await this.REST.post<ReturnTypeBulkCreate<string>>(
|
||||
'/api/beats/enrollment_tokens',
|
||||
{
|
||||
num_tokens: numTokens,
|
||||
}
|
||||
)).results;
|
||||
return results.map(result => result.item);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,13 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import { ReturnTypeBulkAction } from '../../common/return_types';
|
||||
import { CMBeat } from './../../common/domain_types';
|
||||
import {
|
||||
BeatsRemovalReturn,
|
||||
BeatsTagAssignment,
|
||||
CMAssignmentReturn,
|
||||
CMBeatsAdapter,
|
||||
} from './adapters/beats/adapter_types';
|
||||
import { BeatsTagAssignment, CMBeatsAdapter } from './adapters/beats/adapter_types';
|
||||
import { ElasticsearchLib } from './elasticsearch';
|
||||
|
||||
export class BeatsLib {
|
||||
|
@ -56,14 +52,14 @@ export class BeatsLib {
|
|||
/** unassign tags from beats using an array of tags and beats */
|
||||
public removeTagsFromBeats = async (
|
||||
removals: BeatsTagAssignment[]
|
||||
): Promise<BeatsRemovalReturn[]> => {
|
||||
): Promise<ReturnTypeBulkAction['results']> => {
|
||||
return await this.adapter.removeTagsFromBeats(removals);
|
||||
};
|
||||
|
||||
/** assign tags from beats using an array of tags and beats */
|
||||
public assignTagsToBeats = async (
|
||||
assignments: BeatsTagAssignment[]
|
||||
): Promise<CMAssignmentReturn[]> => {
|
||||
): Promise<ReturnTypeBulkAction['results']> => {
|
||||
return await this.adapter.assignTagsToBeats(assignments);
|
||||
};
|
||||
}
|
||||
|
|
|
@ -23,7 +23,7 @@ export class ConfigBlocksLib {
|
|||
|
||||
public getForTags = async (tagIds: string[], page: number) => {
|
||||
const result = await this.adapter.getForTags(tagIds, page);
|
||||
result.blocks = this.jsonConfigToUserYaml(result.blocks);
|
||||
result.list = this.jsonConfigToUserYaml(result.list);
|
||||
return result;
|
||||
};
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ class BeatDetailPageUi extends React.PureComponent<PageProps, PageState> {
|
|||
);
|
||||
|
||||
this.setState({
|
||||
configuration_blocks: blocksResult.blocks,
|
||||
configuration_blocks: blocksResult.list,
|
||||
tags,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -61,7 +61,7 @@ class TagCreatePageComponent extends React.PureComponent<
|
|||
<TagEdit
|
||||
tag={this.state.tag}
|
||||
configuration_blocks={{
|
||||
blocks: this.state.configuration_blocks.slice(
|
||||
list: this.state.configuration_blocks.slice(
|
||||
blockStartingIndex,
|
||||
5 + blockStartingIndex
|
||||
),
|
||||
|
@ -140,8 +140,8 @@ class TagCreatePageComponent extends React.PureComponent<
|
|||
const createBlocksResponse = await this.props.libs.configBlocks.upsert(
|
||||
this.state.configuration_blocks.map(block => ({ ...block, tag: this.state.tag.id }))
|
||||
);
|
||||
const creationError = createBlocksResponse.reduce(
|
||||
(err: string, resp: any) => (!err ? (err = resp.error || '') : err),
|
||||
const creationError = createBlocksResponse.results.reduce(
|
||||
(err: string, resp) => (!err ? (err = resp.error ? resp.error.message : '') : err),
|
||||
''
|
||||
);
|
||||
if (creationError) {
|
||||
|
|
|
@ -22,7 +22,7 @@ interface TagPageState {
|
|||
beatsTags: BeatTag[];
|
||||
configuration_blocks: {
|
||||
error?: string | undefined;
|
||||
blocks: ConfigurationBlock[];
|
||||
list: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
};
|
||||
|
@ -47,7 +47,7 @@ class TagEditPageComponent extends React.PureComponent<
|
|||
hasConfigurationBlocksTypes: [],
|
||||
},
|
||||
configuration_blocks: {
|
||||
blocks: [],
|
||||
list: [],
|
||||
page: 0,
|
||||
total: 0,
|
||||
},
|
||||
|
@ -160,7 +160,12 @@ class TagEditPageComponent extends React.PureComponent<
|
|||
const blocksResponse = await this.props.libs.configBlocks.getForTags([this.state.tag.id], page);
|
||||
|
||||
this.setState({
|
||||
configuration_blocks: blocksResponse,
|
||||
configuration_blocks: blocksResponse as {
|
||||
error?: string | undefined;
|
||||
list: ConfigurationBlock[];
|
||||
page: number;
|
||||
total: number;
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -189,7 +194,7 @@ class TagEditPageComponent extends React.PureComponent<
|
|||
this.props.goTo(`/overview/configuration_tags`);
|
||||
};
|
||||
private getNumExclusiveConfigurationBlocks = () =>
|
||||
this.state.configuration_blocks.blocks
|
||||
this.state.configuration_blocks.list
|
||||
.map(({ type }) => UNIQUENESS_ENFORCING_TYPES.some(uniqueType => uniqueType === type))
|
||||
.reduce((acc, cur) => (cur ? acc + 1 : acc), 0);
|
||||
}
|
||||
|
|
|
@ -45,10 +45,7 @@ export class InitialTagPage extends Component<AppPageProps, PageState> {
|
|||
<TagEdit
|
||||
tag={this.state.tag}
|
||||
configuration_blocks={{
|
||||
blocks: this.state.configuration_blocks.slice(
|
||||
blockStartingIndex,
|
||||
5 + blockStartingIndex
|
||||
),
|
||||
list: this.state.configuration_blocks.slice(blockStartingIndex, 5 + blockStartingIndex),
|
||||
page: this.state.currentConfigPage,
|
||||
total: this.state.configuration_blocks.length,
|
||||
}}
|
||||
|
@ -123,8 +120,8 @@ export class InitialTagPage extends Component<AppPageProps, PageState> {
|
|||
const createBlocksResponse = await this.props.libs.configBlocks.upsert(
|
||||
this.state.configuration_blocks.map(block => ({ ...block, tag: this.state.tag.id }))
|
||||
);
|
||||
const creationError = createBlocksResponse.reduce(
|
||||
(err: string, resp: any) => (!err ? (err = resp.error || '') : err),
|
||||
const creationError = createBlocksResponse.results.reduce(
|
||||
(err: string, resp) => (!err ? (err = resp.error ? resp.error.message : '') : err),
|
||||
''
|
||||
);
|
||||
if (creationError) {
|
||||
|
|
|
@ -1,29 +1,32 @@
|
|||
# Documentation for Beats CM in x-pack kibana
|
||||
# Beats CM
|
||||
|
||||
Notes:
|
||||
Falure to have auth enabled in Kibana will make for a broken UI. UI based errors not yet in place
|
||||
|
||||
### Run tests
|
||||
## Testing
|
||||
|
||||
```
|
||||
node scripts/jest.js plugins/beats --watch
|
||||
```
|
||||
### Unit tests
|
||||
|
||||
and for functional... (from x-pack root)
|
||||
From `~/kibana/x-pack`, run `node scripts/jest.js plugins/beats --watch`.
|
||||
|
||||
```
|
||||
node scripts/functional_tests --config test/api_integration/config
|
||||
```
|
||||
### API tests
|
||||
|
||||
### Run command to fake an enrolling beat (from beats_management dir)
|
||||
In one shell, from **~/kibana/x-pack**:
|
||||
`node scripts/functional_tests-server.js`
|
||||
|
||||
In another shell, from **~kibana/x-pack**:
|
||||
`node ../scripts/functional_test_runner.js --config test/api_integration/config.js`.
|
||||
|
||||
### Manual e2e testing
|
||||
|
||||
- Run this 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
|
||||
- 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>
|
||||
|
|
|
@ -171,6 +171,6 @@ export interface FrameworkRouteOptions<
|
|||
export type FrameworkRouteHandler<
|
||||
RouteRequest extends KibanaServerRequest,
|
||||
RouteResponse extends FrameworkResponse
|
||||
> = (request: FrameworkRequest<RouteRequest>, h: ResponseToolkit) => void;
|
||||
> = (request: FrameworkRequest<RouteRequest>, h: ResponseToolkit) => Promise<RouteResponse>;
|
||||
|
||||
export type FrameworkResponse = Lifecycle.ReturnValue;
|
||||
|
|
|
@ -82,7 +82,6 @@ export class CMBeatsDomain {
|
|||
}
|
||||
|
||||
const user = typeof userOrToken === 'string' ? this.framework.internalUser : userOrToken;
|
||||
|
||||
await this.adapter.update(user, {
|
||||
...beat,
|
||||
...beatData,
|
||||
|
|
|
@ -4,14 +4,13 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import { ResponseObject, ResponseToolkit } from 'hapi';
|
||||
import { difference } from 'lodash';
|
||||
import { BaseReturnType } from '../../common/return_types';
|
||||
import {
|
||||
BackendFrameworkAdapter,
|
||||
FrameworkRequest,
|
||||
FrameworkResponse,
|
||||
FrameworkRouteHandler,
|
||||
FrameworkRouteOptions,
|
||||
} from './adapters/framework/adapter_types';
|
||||
|
||||
export class BackendFrameworkLib {
|
||||
|
@ -26,13 +25,18 @@ export class BackendFrameworkLib {
|
|||
public registerRoute<
|
||||
RouteRequest extends FrameworkRequest,
|
||||
RouteResponse extends FrameworkResponse
|
||||
>(route: FrameworkRouteOptions<RouteRequest, RouteResponse>) {
|
||||
>(route: {
|
||||
path: string;
|
||||
method: string | string[];
|
||||
licenseRequired?: string[];
|
||||
requiredRoles?: string[];
|
||||
handler: (request: FrameworkRequest<RouteRequest>) => Promise<BaseReturnType>;
|
||||
config?: {};
|
||||
}) {
|
||||
this.adapter.registerRoute({
|
||||
...route,
|
||||
handler: this.wrapRouteWithSecurity(
|
||||
route.handler,
|
||||
route.licenseRequired || [],
|
||||
route.requiredRoles
|
||||
handler: this.wrapErrors(
|
||||
this.wrapRouteWithSecurity(route.handler, route.licenseRequired || [], route.requiredRoles)
|
||||
),
|
||||
});
|
||||
}
|
||||
|
@ -71,25 +75,35 @@ export class BackendFrameworkLib {
|
|||
}
|
||||
|
||||
private wrapRouteWithSecurity(
|
||||
handler: FrameworkRouteHandler<any, any>,
|
||||
handler: (request: FrameworkRequest<any>) => Promise<BaseReturnType>,
|
||||
requiredLicense: string[],
|
||||
requiredRoles?: string[]
|
||||
) {
|
||||
return async (request: FrameworkRequest, h: any) => {
|
||||
): (request: FrameworkRequest) => Promise<BaseReturnType> {
|
||||
return async (request: FrameworkRequest) => {
|
||||
if (
|
||||
requiredLicense.length > 0 &&
|
||||
(this.license.expired || !requiredLicense.includes(this.license.type))
|
||||
) {
|
||||
return Boom.forbidden(
|
||||
`Your ${
|
||||
this.license.type
|
||||
} license does not support this API or is expired. Please upgrade your license.`
|
||||
);
|
||||
return {
|
||||
error: {
|
||||
message: `Your ${
|
||||
this.license.type
|
||||
} license does not support this API or is expired. Please upgrade your license.`,
|
||||
code: 403,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (requiredRoles) {
|
||||
if (request.user.kind !== 'authenticated') {
|
||||
return h.response().code(403);
|
||||
return {
|
||||
error: {
|
||||
message: `Request must be authenticated`,
|
||||
code: 403,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
|
@ -97,10 +111,67 @@ export class BackendFrameworkLib {
|
|||
!request.user.roles.includes('superuser') &&
|
||||
difference(requiredRoles, request.user.roles).length !== 0
|
||||
) {
|
||||
return h.response().code(403);
|
||||
return {
|
||||
error: {
|
||||
message: `Request must be authenticated by a user with one of the following user roles: ${requiredRoles.join(
|
||||
','
|
||||
)}`,
|
||||
code: 403,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
}
|
||||
return await handler(request, h);
|
||||
return await handler(request);
|
||||
};
|
||||
}
|
||||
private wrapErrors(
|
||||
handler: (request: FrameworkRequest<any>) => Promise<BaseReturnType>
|
||||
): (request: FrameworkRequest, h: ResponseToolkit) => Promise<ResponseObject> {
|
||||
return async (request: FrameworkRequest, h: ResponseToolkit) => {
|
||||
try {
|
||||
const result = await handler(request);
|
||||
if (!result.error) {
|
||||
return h.response(result);
|
||||
}
|
||||
return h
|
||||
.response({
|
||||
error: result.error,
|
||||
success: false,
|
||||
})
|
||||
.code(result.error.code || 400);
|
||||
} catch (err) {
|
||||
let statusCode = err.statusCode;
|
||||
|
||||
// This is the only known non-status code error in the system, but just in case we have an else
|
||||
if (!statusCode && (err.message as string).includes('Invalid user type')) {
|
||||
statusCode = 403;
|
||||
} else {
|
||||
statusCode = 500;
|
||||
}
|
||||
|
||||
if (statusCode === 403) {
|
||||
return h
|
||||
.response({
|
||||
error: {
|
||||
message: 'Insufficient user permissions for managing Beats configuration',
|
||||
code: 403,
|
||||
},
|
||||
success: false,
|
||||
})
|
||||
.code(403);
|
||||
}
|
||||
|
||||
return h
|
||||
.response({
|
||||
error: {
|
||||
message: err.message,
|
||||
code: statusCode,
|
||||
},
|
||||
success: false,
|
||||
})
|
||||
.code(statusCode);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ describe('assign_tags_to_beats', () => {
|
|||
});
|
||||
|
||||
expect(statusCode).toEqual(200);
|
||||
expect(result.assignments).toEqual([{ status: 200, result: 'updated' }]);
|
||||
expect(result.results).toEqual([{ success: true, result: { message: 'updated' } }]);
|
||||
});
|
||||
|
||||
it('should not re-add an existing tag to a beat', async () => {
|
||||
|
@ -52,7 +52,7 @@ describe('assign_tags_to_beats', () => {
|
|||
|
||||
expect(statusCode).toEqual(200);
|
||||
|
||||
expect(result.assignments).toEqual([{ status: 200, result: 'updated' }]);
|
||||
expect(result.results).toEqual([{ success: true, result: { message: 'updated' } }]);
|
||||
|
||||
const beat = await serverLibs.beats.getById(
|
||||
{
|
||||
|
@ -79,9 +79,9 @@ describe('assign_tags_to_beats', () => {
|
|||
|
||||
expect(statusCode).toEqual(200);
|
||||
|
||||
expect(result.assignments).toEqual([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(result.results).toEqual([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
let beat;
|
||||
|
@ -121,9 +121,9 @@ describe('assign_tags_to_beats', () => {
|
|||
|
||||
expect(statusCode).toEqual(200);
|
||||
|
||||
expect(result.assignments).toEqual([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(result.results).toEqual([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
const beat = await serverLibs.beats.getById(
|
||||
|
|
|
@ -5,8 +5,9 @@
|
|||
*/
|
||||
import Joi from 'joi';
|
||||
import { ConfigurationBlock } from '../../../common/domain_types';
|
||||
import { BaseReturnType, ReturnTypeList } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
|
@ -19,43 +20,43 @@ export const createGetBeatConfigurationRoute = (libs: CMServerLibs) => ({
|
|||
},
|
||||
auth: false,
|
||||
},
|
||||
handler: async (request: any, h: any) => {
|
||||
handler: async (
|
||||
request: FrameworkRequest
|
||||
): Promise<BaseReturnType | ReturnTypeList<ConfigurationBlock>> => {
|
||||
const beatId = request.params.beatId;
|
||||
const accessToken = request.headers['kbn-beats-access-token'];
|
||||
|
||||
let configurationBlocks: ConfigurationBlock[];
|
||||
try {
|
||||
const beat = await libs.beats.getById(libs.framework.internalUser, beatId);
|
||||
if (beat === null) {
|
||||
return h.response({ message: `Beat "${beatId}" not found` }).code(404);
|
||||
}
|
||||
|
||||
const isAccessTokenValid = beat.access_token === accessToken;
|
||||
if (!isAccessTokenValid) {
|
||||
return h.response({ message: 'Invalid access token' }).code(401);
|
||||
}
|
||||
const beat = await libs.beats.getById(libs.framework.internalUser, beatId);
|
||||
if (beat === null) {
|
||||
return { error: { message: `Beat "${beatId}" not found`, code: 404 }, success: false };
|
||||
}
|
||||
|
||||
await libs.beats.update(libs.framework.internalUser, beat.id, {
|
||||
last_checkin: new Date(),
|
||||
});
|
||||
const isAccessTokenValid = beat.access_token === accessToken;
|
||||
if (!isAccessTokenValid) {
|
||||
return { error: { message: 'Invalid access token', code: 401 }, success: false };
|
||||
}
|
||||
|
||||
if (beat.tags) {
|
||||
const result = await libs.configurationBlocks.getForTags(
|
||||
libs.framework.internalUser,
|
||||
beat.tags,
|
||||
-1
|
||||
);
|
||||
await libs.beats.update(libs.framework.internalUser, beat.id, {
|
||||
last_checkin: new Date(),
|
||||
});
|
||||
|
||||
configurationBlocks = result.blocks;
|
||||
} else {
|
||||
configurationBlocks = [];
|
||||
}
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
if (beat.tags) {
|
||||
const result = await libs.configurationBlocks.getForTags(
|
||||
libs.framework.internalUser,
|
||||
beat.tags,
|
||||
-1
|
||||
);
|
||||
|
||||
configurationBlocks = result.blocks;
|
||||
} else {
|
||||
configurationBlocks = [];
|
||||
}
|
||||
|
||||
return {
|
||||
configuration_blocks: configurationBlocks,
|
||||
list: configurationBlocks,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,9 +6,10 @@
|
|||
import Joi from 'joi';
|
||||
import { omit } from 'lodash';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { CMBeat } from '../../../common/domain_types';
|
||||
import { BaseReturnType, ReturnTypeCreate } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { BeatEnrollmentStatus, CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
// TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024
|
||||
export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({
|
||||
|
@ -31,38 +32,38 @@ export const createBeatEnrollmentRoute = (libs: CMServerLibs) => ({
|
|||
}).required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest, h: any) => {
|
||||
handler: async (
|
||||
request: FrameworkRequest
|
||||
): Promise<BaseReturnType | ReturnTypeCreate<CMBeat>> => {
|
||||
const { beatId } = request.params;
|
||||
const enrollmentToken = request.headers['kbn-beats-enrollment-token'];
|
||||
|
||||
try {
|
||||
const { status, accessToken } = await libs.beats.enrollBeat(
|
||||
enrollmentToken,
|
||||
beatId,
|
||||
request.info.remoteAddress,
|
||||
omit(request.payload, 'enrollment_token')
|
||||
);
|
||||
const { status, accessToken } = await libs.beats.enrollBeat(
|
||||
enrollmentToken,
|
||||
beatId,
|
||||
request.info.remoteAddress,
|
||||
omit(request.payload, 'enrollment_token')
|
||||
);
|
||||
|
||||
switch (status) {
|
||||
case BeatEnrollmentStatus.ExpiredEnrollmentToken:
|
||||
return h
|
||||
.response({
|
||||
message: BeatEnrollmentStatus.ExpiredEnrollmentToken,
|
||||
})
|
||||
.code(400);
|
||||
case BeatEnrollmentStatus.InvalidEnrollmentToken:
|
||||
return h
|
||||
.response({
|
||||
message: BeatEnrollmentStatus.InvalidEnrollmentToken,
|
||||
})
|
||||
.code(400);
|
||||
case BeatEnrollmentStatus.Success:
|
||||
default:
|
||||
return h.response({ access_token: accessToken }).code(201);
|
||||
}
|
||||
} catch (err) {
|
||||
// FIXME move this to kibana route thing in adapter
|
||||
return wrapEsError(err);
|
||||
switch (status) {
|
||||
case BeatEnrollmentStatus.ExpiredEnrollmentToken:
|
||||
return {
|
||||
error: { message: BeatEnrollmentStatus.ExpiredEnrollmentToken, code: 400 },
|
||||
success: false,
|
||||
};
|
||||
|
||||
case BeatEnrollmentStatus.InvalidEnrollmentToken:
|
||||
return {
|
||||
error: { message: BeatEnrollmentStatus.InvalidEnrollmentToken, code: 400 },
|
||||
success: false,
|
||||
};
|
||||
case BeatEnrollmentStatus.Success:
|
||||
default:
|
||||
return {
|
||||
item: accessToken,
|
||||
action: 'created',
|
||||
success: true,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,8 +4,9 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
import Joi from 'joi';
|
||||
import { BaseReturnType, ReturnTypeBulkAction } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const beatEventsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'POST',
|
||||
|
@ -18,29 +19,26 @@ export const beatEventsRoute = (libs: CMServerLibs) => ({
|
|||
},
|
||||
auth: false,
|
||||
},
|
||||
handler: async (request: any, h: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<BaseReturnType | ReturnTypeBulkAction> => {
|
||||
const beatId = request.params.beatId;
|
||||
const events = request.payload;
|
||||
const accessToken = request.headers['kbn-beats-access-token'];
|
||||
|
||||
try {
|
||||
const beat = await libs.beats.getById(libs.framework.internalUser, beatId);
|
||||
if (beat === null) {
|
||||
return h.response({ message: `Beat "${beatId}" not found` }).code(400);
|
||||
}
|
||||
|
||||
const isAccessTokenValid = beat.access_token === accessToken;
|
||||
if (!isAccessTokenValid) {
|
||||
return h.response({ message: 'Invalid access token' }).code(401);
|
||||
}
|
||||
|
||||
const results = await libs.beatEvents.log(libs.framework.internalUser, beat.id, events);
|
||||
|
||||
return {
|
||||
response: results,
|
||||
};
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
const beat = await libs.beats.getById(libs.framework.internalUser, beatId);
|
||||
if (beat === null) {
|
||||
return { error: { message: `Beat "${beatId}" not found`, code: 400 }, success: false };
|
||||
}
|
||||
|
||||
const isAccessTokenValid = beat.access_token === accessToken;
|
||||
if (!isAccessTokenValid) {
|
||||
return { error: { message: `Invalid access token`, code: 401 }, success: false };
|
||||
}
|
||||
|
||||
const results = await libs.beatEvents.log(libs.framework.internalUser, beat.id, events);
|
||||
|
||||
return {
|
||||
results,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,39 +5,35 @@
|
|||
*/
|
||||
|
||||
import { CMBeat } from '../../../common/domain_types';
|
||||
import { BaseReturnType, ReturnTypeGet } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createGetBeatRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/beats/agent/{beatId}/{token?}',
|
||||
requiredRoles: ['beats_admin'],
|
||||
handler: async (request: any, h: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<BaseReturnType | ReturnTypeGet<CMBeat>> => {
|
||||
const beatId = request.params.beatId;
|
||||
|
||||
let beat: CMBeat | null;
|
||||
if (beatId === 'unknown') {
|
||||
try {
|
||||
beat = await libs.beats.getByEnrollmentToken(request.user, request.params.token);
|
||||
if (beat === null) {
|
||||
return h.response().code(200);
|
||||
}
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
beat = await libs.beats.getByEnrollmentToken(request.user, request.params.token);
|
||||
if (beat === null) {
|
||||
return { success: false };
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
beat = await libs.beats.getById(request.user, beatId);
|
||||
if (beat === null) {
|
||||
return h.response({ message: 'Beat not found' }).code(404);
|
||||
}
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
beat = await libs.beats.getById(request.user, beatId);
|
||||
if (beat === null) {
|
||||
return { error: { message: 'Beat not found', code: 404 }, success: false };
|
||||
}
|
||||
}
|
||||
|
||||
delete beat.access_token;
|
||||
|
||||
return beat;
|
||||
return {
|
||||
item: beat,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -7,9 +7,9 @@
|
|||
import * as Joi from 'joi';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { CMBeat } from '../../../common/domain_types';
|
||||
import { ReturnTypeList } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createListAgentsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
|
@ -27,7 +27,7 @@ export const createListAgentsRoute = (libs: CMServerLibs) => ({
|
|||
ESQuery: Joi.string(),
|
||||
}),
|
||||
},
|
||||
handler: async (request: FrameworkRequest) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeList<CMBeat>> => {
|
||||
const listByAndValueParts = request.params.listByAndValue
|
||||
? request.params.listByAndValue.split('/')
|
||||
: [];
|
||||
|
@ -39,27 +39,22 @@ export const createListAgentsRoute = (libs: CMServerLibs) => ({
|
|||
listByValue = listByAndValueParts[1];
|
||||
}
|
||||
|
||||
try {
|
||||
let beats: CMBeat[];
|
||||
let beats: CMBeat[];
|
||||
|
||||
switch (listBy) {
|
||||
case 'tag':
|
||||
beats = await libs.beats.getAllWithTag(request.user, listByValue || '');
|
||||
break;
|
||||
switch (listBy) {
|
||||
case 'tag':
|
||||
beats = await libs.beats.getAllWithTag(request.user, listByValue || '');
|
||||
break;
|
||||
|
||||
default:
|
||||
beats = await libs.beats.getAll(
|
||||
request.user,
|
||||
request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined
|
||||
);
|
||||
default:
|
||||
beats = await libs.beats.getAll(
|
||||
request.user,
|
||||
request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined
|
||||
);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return { beats };
|
||||
} catch (err) {
|
||||
// FIXME move this to kibana route thing in adapter
|
||||
return wrapEsError(err);
|
||||
break;
|
||||
}
|
||||
|
||||
return { list: beats, success: true, page: -1, total: -1 };
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,10 +6,10 @@
|
|||
|
||||
import Joi from 'joi';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { ReturnTypeBulkAction } from '../../../common/return_types';
|
||||
import { BeatsTagAssignment } from '../../../public/lib/adapters/beats/adapter_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
// TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024
|
||||
export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({
|
||||
|
@ -29,15 +29,29 @@ export const createTagAssignmentsRoute = (libs: CMServerLibs) => ({
|
|||
}).required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkAction> => {
|
||||
const { assignments }: { assignments: BeatsTagAssignment[] } = request.payload;
|
||||
|
||||
try {
|
||||
const response = await libs.beats.assignTagsToBeats(request.user, assignments);
|
||||
return response;
|
||||
} catch (err) {
|
||||
// TODO move this to kibana route thing in adapter
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const response = await libs.beats.assignTagsToBeats(request.user, assignments);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results: response.assignments.map(assignment => ({
|
||||
success: assignment.status && assignment.status >= 200 && assignment.status < 300,
|
||||
error:
|
||||
!assignment.status || assignment.status >= 300
|
||||
? {
|
||||
code: assignment.status || 400,
|
||||
message: assignment.result,
|
||||
}
|
||||
: undefined,
|
||||
result:
|
||||
assignment.status && assignment.status >= 200 && assignment.status < 300
|
||||
? {
|
||||
message: assignment.result,
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
} as ReturnTypeBulkAction;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import Joi from 'joi';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { ReturnTypeBulkAction } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
// TODO: write to Kibana audit log file https://github.com/elastic/kibana/issues/26024
|
||||
export const createTagRemovalsRoute = (libs: CMServerLibs) => ({
|
||||
|
@ -28,15 +28,29 @@ export const createTagRemovalsRoute = (libs: CMServerLibs) => ({
|
|||
}).required(),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkAction> => {
|
||||
const { removals } = request.payload;
|
||||
|
||||
try {
|
||||
const response = await libs.beats.removeTagsFromBeats(request.user, removals);
|
||||
return response;
|
||||
} catch (err) {
|
||||
// TODO move this to kibana route thing in adapter
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const response = await libs.beats.removeTagsFromBeats(request.user, removals);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results: response.removals.map(removal => ({
|
||||
success: removal.status && removal.status >= 200 && removal.status < 300,
|
||||
error:
|
||||
!removal.status || removal.status >= 300
|
||||
? {
|
||||
code: removal.status || 400,
|
||||
message: removal.result,
|
||||
}
|
||||
: undefined,
|
||||
result:
|
||||
removal.status && removal.status >= 200 && removal.status < 300
|
||||
? {
|
||||
message: removal.result,
|
||||
}
|
||||
: undefined,
|
||||
})),
|
||||
} as ReturnTypeBulkAction;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
import Joi from 'joi';
|
||||
import { CMBeat } from '../../../common/domain_types';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { BaseReturnType, ReturnTypeUpdate } from '../../../common/return_types';
|
||||
import { FrameworkRequest, internalUser } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
// TODO: write to Kibana audit log file (include who did the verification as well) https://github.com/elastic/kibana/issues/26024
|
||||
export const createBeatUpdateRoute = (libs: CMServerLibs) => ({
|
||||
|
@ -38,34 +39,54 @@ export const createBeatUpdateRoute = (libs: CMServerLibs) => ({
|
|||
}),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest, h: any) => {
|
||||
handler: async (
|
||||
request: FrameworkRequest
|
||||
): Promise<BaseReturnType | ReturnTypeUpdate<CMBeat>> => {
|
||||
const { beatId } = request.params;
|
||||
const accessToken = request.headers['kbn-beats-access-token'];
|
||||
const remoteAddress = request.info.remoteAddress;
|
||||
const userOrToken = accessToken || request.user;
|
||||
|
||||
if (request.user.kind === 'unauthenticated' && request.payload.active !== undefined) {
|
||||
return h
|
||||
.response({ message: 'access-token is not a valid auth type to change beat status' })
|
||||
.code(401);
|
||||
return {
|
||||
error: {
|
||||
message: 'access-token is not a valid auth type to change beat status',
|
||||
code: 401,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const status = await libs.beats.update(userOrToken, beatId, {
|
||||
...request.payload,
|
||||
host_ip: remoteAddress,
|
||||
});
|
||||
const status = await libs.beats.update(userOrToken, beatId, {
|
||||
...request.payload,
|
||||
host_ip: remoteAddress,
|
||||
});
|
||||
|
||||
switch (status) {
|
||||
case 'beat-not-found':
|
||||
return h.response({ message: 'Beat not found', success: false }).code(404);
|
||||
case 'invalid-access-token':
|
||||
return h.response({ message: 'Invalid access token', success: false }).code(401);
|
||||
}
|
||||
|
||||
return h.response({ success: true }).code(204);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
switch (status) {
|
||||
case 'beat-not-found':
|
||||
return {
|
||||
error: {
|
||||
message: 'Beat not found',
|
||||
code: 404,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
case 'invalid-access-token':
|
||||
return {
|
||||
error: {
|
||||
message: 'Invalid access token',
|
||||
code: 401,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
|
||||
const beat = await libs.beats.getById(internalUser, beatId);
|
||||
|
||||
return {
|
||||
item: beat,
|
||||
action: 'updated',
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,22 +5,28 @@
|
|||
*/
|
||||
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { ReturnTypeBulkDelete } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createDeleteConfidurationsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'DELETE',
|
||||
path: '/api/beats/configurations/{ids}',
|
||||
requiredRoles: ['beats_admin'],
|
||||
licenseRequired: REQUIRED_LICENSES,
|
||||
handler: async (request: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkDelete> => {
|
||||
const idString: string = request.params.ids;
|
||||
const ids = idString.split(',').filter((id: string) => id.length > 0);
|
||||
|
||||
try {
|
||||
return await libs.configurationBlocks.delete(request.user, ids);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const results = await libs.configurationBlocks.delete(request.user, ids);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
results: results.map(result => ({
|
||||
success: result.success,
|
||||
action: 'deleted',
|
||||
error: result.success ? undefined : { message: result.reason },
|
||||
})),
|
||||
} as ReturnTypeBulkDelete;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,31 +5,27 @@
|
|||
*/
|
||||
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { ConfigurationBlock } from '../../../common/domain_types';
|
||||
import { ReturnTypeList } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
import { FrameworkRouteOptions } from './../../lib/adapters/framework/adapter_types';
|
||||
|
||||
export const createGetConfigurationBlocksRoute = (libs: CMServerLibs): FrameworkRouteOptions => ({
|
||||
export const createGetConfigurationBlocksRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/beats/configurations/{tagIds}/{page?}',
|
||||
requiredRoles: ['beats_admin'],
|
||||
licenseRequired: REQUIRED_LICENSES,
|
||||
handler: async (request: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeList<ConfigurationBlock>> => {
|
||||
const tagIdString: string = request.params.tagIds;
|
||||
const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0);
|
||||
|
||||
let tags;
|
||||
try {
|
||||
tags = await libs.configurationBlocks.getForTags(
|
||||
request.user,
|
||||
tagIds,
|
||||
parseInt(request.params.page, 10),
|
||||
5
|
||||
);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const result = await libs.configurationBlocks.getForTags(
|
||||
request.user,
|
||||
tagIds,
|
||||
parseInt(request.params.page, 10),
|
||||
5
|
||||
);
|
||||
|
||||
return tags;
|
||||
return { page: result.page, total: result.total, list: result.blocks, success: true };
|
||||
},
|
||||
});
|
||||
|
|
|
@ -16,6 +16,7 @@ import {
|
|||
ConfigurationBlock,
|
||||
createConfigurationBlockInterface,
|
||||
} from '../../../common/domain_types';
|
||||
import { ReturnTypeBulkUpsert } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
|
||||
|
@ -30,16 +31,16 @@ export const upsertConfigurationRoute = (libs: CMServerLibs) => ({
|
|||
payload: Joi.array().items(Joi.object({}).unknown(true)),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest) => {
|
||||
const result = request.payload.map(async (block: ConfigurationBlock) => {
|
||||
const assertData = createConfigurationBlockInterface().decode(block);
|
||||
if (assertData.isLeft()) {
|
||||
return {
|
||||
error: `Error parsing block info, ${PathReporter.report(assertData)[0]}`,
|
||||
};
|
||||
}
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkUpsert> => {
|
||||
const result = await Promise.all<any>(
|
||||
request.payload.map(async (block: ConfigurationBlock) => {
|
||||
const assertData = createConfigurationBlockInterface().decode(block);
|
||||
if (assertData.isLeft()) {
|
||||
return {
|
||||
error: `Error parsing block info, ${PathReporter.report(assertData)[0]}`,
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
const { blockID, success, error } = await libs.configurationBlocks.save(
|
||||
request.user,
|
||||
block
|
||||
|
@ -49,11 +50,16 @@ export const upsertConfigurationRoute = (libs: CMServerLibs) => ({
|
|||
}
|
||||
|
||||
return { success, blockID };
|
||||
} catch (err) {
|
||||
return { success: false, error: err.msg };
|
||||
}
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
return Promise.all(result);
|
||||
return {
|
||||
results: result.map(r => ({
|
||||
success: r.success as boolean,
|
||||
// TODO: we need to surface this data, not hard coded
|
||||
action: 'created' as 'created' | 'updated',
|
||||
})),
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -7,29 +7,28 @@
|
|||
import { flatten } from 'lodash';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { BeatTag } from '../../../common/domain_types';
|
||||
import { ReturnTypeBulkGet } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createAssignableTagsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/beats/tags/assignable/{beatIds}',
|
||||
requiredRoles: ['beats_admin'],
|
||||
licenseRequired: REQUIRED_LICENSES,
|
||||
handler: async (request: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkGet<BeatTag>> => {
|
||||
const beatIdString: string = request.params.beatIds;
|
||||
const beatIds = beatIdString.split(',').filter((id: string) => id.length > 0);
|
||||
|
||||
let tags: BeatTag[];
|
||||
try {
|
||||
const beats = await libs.beats.getByIds(request.user, beatIds);
|
||||
tags = await libs.tags.getNonConflictingTags(
|
||||
request.user,
|
||||
flatten(beats.map(beat => beat.tags))
|
||||
);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const beats = await libs.beats.getByIds(request.user, beatIds);
|
||||
const tags = await libs.tags.getNonConflictingTags(
|
||||
request.user,
|
||||
flatten(beats.map(beat => beat.tags))
|
||||
);
|
||||
|
||||
return tags;
|
||||
return {
|
||||
items: tags,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,25 +5,27 @@
|
|||
*/
|
||||
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { ReturnTypeBulkDelete } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createDeleteTagsWithIdsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'DELETE',
|
||||
path: '/api/beats/tags/{tagIds}',
|
||||
requiredRoles: ['beats_admin'],
|
||||
licenseRequired: REQUIRED_LICENSES,
|
||||
handler: async (request: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkDelete> => {
|
||||
const tagIdString: string = request.params.tagIds;
|
||||
const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0);
|
||||
|
||||
let success: boolean;
|
||||
try {
|
||||
success = await libs.tags.delete(request.user, tagIds);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const success = await libs.tags.delete(request.user, tagIds);
|
||||
|
||||
return { success };
|
||||
return {
|
||||
results: tagIds.map(() => ({
|
||||
success,
|
||||
action: 'deleted',
|
||||
})),
|
||||
success,
|
||||
} as ReturnTypeBulkDelete;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,26 +6,24 @@
|
|||
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { BeatTag } from '../../../common/domain_types';
|
||||
import { ReturnTypeBulkGet } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
import { FrameworkRouteOptions } from './../../lib/adapters/framework/adapter_types';
|
||||
|
||||
export const createGetTagsWithIdsRoute = (libs: CMServerLibs): FrameworkRouteOptions => ({
|
||||
export const createGetTagsWithIdsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
path: '/api/beats/tags/{tagIds}',
|
||||
requiredRoles: ['beats_admin'],
|
||||
licenseRequired: REQUIRED_LICENSES,
|
||||
handler: async (request: any) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeBulkGet<BeatTag>> => {
|
||||
const tagIdString: string = request.params.tagIds;
|
||||
const tagIds = tagIdString.split(',').filter((id: string) => id.length > 0);
|
||||
|
||||
let tags: BeatTag[];
|
||||
try {
|
||||
tags = await libs.tags.getWithIds(request.user, tagIds);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
const tags = await libs.tags.getWithIds(request.user, tagIds);
|
||||
|
||||
return tags;
|
||||
return {
|
||||
items: tags,
|
||||
success: true,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
|
|
@ -5,10 +5,11 @@
|
|||
*/
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ReturnTypeList } from '../../../common/return_types';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { BeatTag } from '../../../common/domain_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
export const createListTagsRoute = (libs: CMServerLibs) => ({
|
||||
method: 'GET',
|
||||
|
@ -25,17 +26,12 @@ export const createListTagsRoute = (libs: CMServerLibs) => ({
|
|||
ESQuery: Joi.string(),
|
||||
}),
|
||||
},
|
||||
handler: async (request: any) => {
|
||||
let tags: BeatTag[];
|
||||
try {
|
||||
tags = await libs.tags.getAll(
|
||||
request.user,
|
||||
request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined
|
||||
);
|
||||
} catch (err) {
|
||||
return wrapEsError(err);
|
||||
}
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeList<BeatTag>> => {
|
||||
const tags = await libs.tags.getAll(
|
||||
request.user,
|
||||
request.query && request.query.ESQuery ? JSON.parse(request.query.ESQuery) : undefined
|
||||
);
|
||||
|
||||
return tags;
|
||||
return { list: tags, success: true, page: -1, total: -1 };
|
||||
},
|
||||
});
|
||||
|
|
|
@ -6,10 +6,11 @@
|
|||
|
||||
import Joi from 'joi';
|
||||
import { get } from 'lodash';
|
||||
import { BeatTag } from '../../../common/domain_types';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants';
|
||||
import { ReturnTypeUpsert } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
import { wrapEsError } from '../../utils/error_wrappers';
|
||||
|
||||
// TODO: write to Kibana audit log file
|
||||
export const createSetTagRoute = (libs: CMServerLibs) => ({
|
||||
|
@ -28,7 +29,7 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({
|
|||
}),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest) => {
|
||||
handler: async (request: FrameworkRequest): Promise<ReturnTypeUpsert<BeatTag>> => {
|
||||
const defaultConfig = {
|
||||
id: request.params.tagId,
|
||||
name: request.params.tagId,
|
||||
|
@ -37,13 +38,10 @@ export const createSetTagRoute = (libs: CMServerLibs) => ({
|
|||
};
|
||||
const config = { ...defaultConfig, ...get(request, 'payload', {}) };
|
||||
|
||||
try {
|
||||
const id = await libs.tags.upsertTag(request.user, config);
|
||||
const id = await libs.tags.upsertTag(request.user, config);
|
||||
const tag = await libs.tags.getWithIds(request.user, [id]);
|
||||
|
||||
return { success: true, id };
|
||||
} catch (err) {
|
||||
// TODO move this to kibana route thing in adapter
|
||||
return wrapEsError(err);
|
||||
}
|
||||
// TODO the action needs to be surfaced
|
||||
return { success: true, item: tag[0], action: 'created' };
|
||||
},
|
||||
});
|
||||
|
|
|
@ -4,10 +4,10 @@
|
|||
* you may not use this file except in compliance with the Elastic License.
|
||||
*/
|
||||
|
||||
import Boom from 'boom';
|
||||
import Joi from 'joi';
|
||||
import { get } from 'lodash';
|
||||
import { REQUIRED_LICENSES } from '../../../common/constants/security';
|
||||
import { BaseReturnType, ReturnTypeBulkCreate } from '../../../common/return_types';
|
||||
import { FrameworkRequest } from '../../lib/adapters/framework/adapter_types';
|
||||
import { CMServerLibs } from '../../lib/types';
|
||||
|
||||
|
@ -28,15 +28,30 @@ export const createTokensRoute = (libs: CMServerLibs) => ({
|
|||
}).allow(null),
|
||||
},
|
||||
},
|
||||
handler: async (request: FrameworkRequest) => {
|
||||
handler: async (
|
||||
request: FrameworkRequest
|
||||
): Promise<BaseReturnType | ReturnTypeBulkCreate<string>> => {
|
||||
const numTokens = get(request, 'payload.num_tokens', DEFAULT_NUM_TOKENS);
|
||||
|
||||
try {
|
||||
const tokens = await libs.tokens.createEnrollmentTokens(request.user, numTokens);
|
||||
return { tokens };
|
||||
return {
|
||||
results: tokens.map(token => ({
|
||||
item: token,
|
||||
success: true,
|
||||
action: 'created',
|
||||
})),
|
||||
success: true,
|
||||
};
|
||||
} catch (err) {
|
||||
libs.framework.log(err.message);
|
||||
return Boom.internal();
|
||||
return {
|
||||
error: {
|
||||
message: 'An error occured, please check your Kibana logs',
|
||||
code: 500,
|
||||
},
|
||||
success: false,
|
||||
};
|
||||
}
|
||||
},
|
||||
});
|
||||
|
|
|
@ -1,7 +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.
|
||||
*/
|
||||
|
||||
export { wrapEsError } from './wrap_es_error';
|
|
@ -1,42 +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.
|
||||
*/
|
||||
|
||||
import { wrapEsError } from './wrap_es_error';
|
||||
|
||||
describe('wrap_es_error', () => {
|
||||
describe('#wrapEsError', () => {
|
||||
let originalError: any;
|
||||
beforeEach(() => {
|
||||
originalError = new Error('I am an error');
|
||||
originalError.statusCode = 404;
|
||||
});
|
||||
|
||||
it('should return a Boom object', () => {
|
||||
const wrappedError = wrapEsError(originalError);
|
||||
|
||||
expect(wrappedError.isBoom).toEqual(true);
|
||||
});
|
||||
|
||||
it('should return the correct Boom object', () => {
|
||||
const wrappedError = wrapEsError(originalError);
|
||||
|
||||
expect(wrappedError.output.statusCode).toEqual(originalError.statusCode);
|
||||
expect(wrappedError.output.payload.message).toEqual(originalError.message);
|
||||
});
|
||||
|
||||
it('should return invalid permissions message for 403 errors', () => {
|
||||
const securityError = new Error('I am an error');
|
||||
// @ts-ignore
|
||||
securityError.statusCode = 403;
|
||||
const wrappedError = wrapEsError(securityError);
|
||||
|
||||
expect(wrappedError.isBoom).toEqual(true);
|
||||
expect(wrappedError.message).toEqual(
|
||||
'Insufficient user permissions for managing Beats configuration'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,24 +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.
|
||||
*/
|
||||
|
||||
// @ts-ignore
|
||||
import Boom from 'boom';
|
||||
|
||||
/**
|
||||
* Wraps ES errors into a Boom error response and returns it
|
||||
* This also handles the permissions issue gracefully
|
||||
*
|
||||
* @param err Object ES error
|
||||
* @return Object Boom error response
|
||||
*/
|
||||
export function wrapEsError(err: any) {
|
||||
const statusCode = err.statusCode;
|
||||
if (statusCode === 403) {
|
||||
return Boom.forbidden('Insufficient user permissions for managing Beats configuration');
|
||||
}
|
||||
|
||||
return Boom.boomify(err, { statusCode: err.statusCode });
|
||||
}
|
|
@ -28,7 +28,7 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([{ status: 200, result: 'updated' }]);
|
||||
expect(apiResponse.results).to.eql([{ success: true, result: { message: 'updated' } }]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -63,7 +63,7 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([{ status: 200, result: 'updated' }]);
|
||||
expect(apiResponse.results).to.eql([{ success: true, result: { message: 'updated' } }]);
|
||||
|
||||
// After adding the existing tag
|
||||
esResponse = await es.get({
|
||||
|
@ -87,9 +87,9 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
let esResponse;
|
||||
|
@ -126,9 +126,9 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
|
@ -152,9 +152,9 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
let esResponse;
|
||||
|
@ -191,8 +191,8 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([
|
||||
{ status: 404, result: `Beat ${nonExistentBeatId} not found` },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: false, error: { code: 404, message: `Beat ${nonExistentBeatId} not found` } },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -207,8 +207,8 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([
|
||||
{ status: 404, result: `Tag ${nonExistentTag} not found` },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: false, error: { code: 404, message: `Tag ${nonExistentTag} not found` } },
|
||||
]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
|
@ -232,8 +232,14 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.assignments).to.eql([
|
||||
{ status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: 404,
|
||||
message: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found`,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
|
|
|
@ -20,7 +20,7 @@ export default function ({ getService }) {
|
|||
.send()
|
||||
.expect(200);
|
||||
|
||||
const tokensFromApi = apiResponse.tokens;
|
||||
const tokensFromApi = apiResponse.results.map(r => r.item);
|
||||
|
||||
const esResponse = await es.search({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -44,7 +44,7 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
const tokensFromApi = apiResponse.tokens;
|
||||
const tokensFromApi = apiResponse.results.map(r => r.item);
|
||||
|
||||
const esResponse = await es.search({
|
||||
index: ES_INDEX_NAME,
|
||||
|
|
|
@ -58,7 +58,7 @@ export default function ({ getService }) {
|
|||
.set('kbn-xsrf', 'xxx')
|
||||
.set('kbn-beats-enrollment-token', validEnrollmentToken)
|
||||
.send(beat)
|
||||
.expect(201);
|
||||
.expect(200);
|
||||
|
||||
const esResponse = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -75,9 +75,9 @@ export default function ({ getService }) {
|
|||
.set('kbn-xsrf', 'xxx')
|
||||
.set('kbn-beats-enrollment-token', validEnrollmentToken)
|
||||
.send(beat)
|
||||
.expect(201);
|
||||
.expect(200);
|
||||
|
||||
const accessTokenFromApi = apiResponse.access_token;
|
||||
const accessTokenFromApi = apiResponse.item;
|
||||
|
||||
const esResponse = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -98,7 +98,10 @@ export default function ({ getService }) {
|
|||
.send(beat)
|
||||
.expect(400);
|
||||
|
||||
expect(apiResponse).to.eql({ message: 'Invalid enrollment token' });
|
||||
expect(apiResponse).to.eql({
|
||||
success: false,
|
||||
error: { code: 400, message: 'Invalid enrollment token' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should reject an expired enrollment token', async () => {
|
||||
|
@ -128,7 +131,10 @@ export default function ({ getService }) {
|
|||
.send(beat)
|
||||
.expect(400);
|
||||
|
||||
expect(apiResponse).to.eql({ message: 'Expired enrollment token' });
|
||||
expect(apiResponse).to.eql({
|
||||
success: false,
|
||||
error: { code: 400, message: 'Expired enrollment token' },
|
||||
});
|
||||
});
|
||||
|
||||
it('should delete the given enrollment token so it may not be reused', async () => {
|
||||
|
@ -137,7 +143,7 @@ export default function ({ getService }) {
|
|||
.set('kbn-xsrf', 'xxx')
|
||||
.set('kbn-beats-enrollment-token', validEnrollmentToken)
|
||||
.send(beat)
|
||||
.expect(201);
|
||||
.expect(200);
|
||||
|
||||
const esResponse = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -154,7 +160,7 @@ export default function ({ getService }) {
|
|||
.set('kbn-xsrf', 'xxx')
|
||||
.set('kbn-beats-enrollment-token', validEnrollmentToken)
|
||||
.send(beat)
|
||||
.expect(201);
|
||||
.expect(200);
|
||||
|
||||
await es.index({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -175,7 +181,7 @@ export default function ({ getService }) {
|
|||
.set('kbn-xsrf', 'xxx')
|
||||
.set('kbn-beats-enrollment-token', validEnrollmentToken)
|
||||
.send(beat)
|
||||
.expect(201);
|
||||
.expect(200);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
|
@ -47,7 +47,7 @@ export default function ({ getService }) {
|
|||
)
|
||||
.expect(200);
|
||||
|
||||
const configurationBlocks = apiResponse.configuration_blocks;
|
||||
const configurationBlocks = apiResponse.list;
|
||||
|
||||
expect(configurationBlocks).to.be.an(Array);
|
||||
expect(configurationBlocks.length).to.be(0);
|
||||
|
@ -64,7 +64,7 @@ export default function ({ getService }) {
|
|||
)
|
||||
.expect(200);
|
||||
|
||||
const configurationBlocks = apiResponse.configuration_blocks;
|
||||
const configurationBlocks = apiResponse.list;
|
||||
|
||||
expect(configurationBlocks).to.be.an(Array);
|
||||
expect(configurationBlocks.length).to.be(3);
|
||||
|
|
|
@ -10,10 +10,11 @@ export default function ({ getService, loadTestFile }) {
|
|||
const es = getService('es');
|
||||
|
||||
describe('beats', () => {
|
||||
const cleanup = () => es.indices.delete({
|
||||
index: ES_INDEX_NAME,
|
||||
ignore: [404]
|
||||
});
|
||||
const cleanup = () =>
|
||||
es.indices.delete({
|
||||
index: ES_INDEX_NAME,
|
||||
ignore: [404],
|
||||
});
|
||||
|
||||
beforeEach(cleanup);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ export default function ({ getService }) {
|
|||
it('should return all beats', async () => {
|
||||
const { body: apiResponse } = await supertest.get('/api/beats/agents').expect(200);
|
||||
|
||||
const beatsFromApi = apiResponse.beats;
|
||||
const beatsFromApi = apiResponse.list;
|
||||
|
||||
expect(beatsFromApi.length).to.be(4);
|
||||
expect(beatsFromApi.filter(beat => beat.hasOwnProperty('verified_on')).length).to.be(1);
|
||||
|
@ -29,7 +29,7 @@ export default function ({ getService }) {
|
|||
it('should not return access tokens', async () => {
|
||||
const { body: apiResponse } = await supertest.get('/api/beats/agents').expect(200);
|
||||
|
||||
const beatsFromApi = apiResponse.beats;
|
||||
const beatsFromApi = apiResponse.list;
|
||||
|
||||
expect(beatsFromApi.length).to.be(4);
|
||||
expect(beatsFromApi.filter(beat => beat.hasOwnProperty('access_token')).length).to.be(0);
|
||||
|
|
|
@ -28,7 +28,7 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([{ status: 200, result: 'updated' }]);
|
||||
expect(apiResponse.results).to.eql([{ success: true, result: { message: 'updated' } }]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -48,9 +48,9 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
let esResponse;
|
||||
|
@ -84,9 +84,9 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
|
@ -107,9 +107,9 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([
|
||||
{ status: 200, result: 'updated' },
|
||||
{ status: 200, result: 'updated' },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
{ success: true, result: { message: 'updated' } },
|
||||
]);
|
||||
|
||||
let esResponse;
|
||||
|
@ -145,8 +145,8 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([
|
||||
{ status: 404, result: `Beat ${nonExistentBeatId} not found` },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: false, error: { code: 404, message: `Beat ${nonExistentBeatId} not found` } },
|
||||
]);
|
||||
});
|
||||
|
||||
|
@ -161,8 +161,8 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([
|
||||
{ status: 404, result: `Tag ${nonExistentTag} not found` },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{ success: false, error: { code: 404, message: `Tag ${nonExistentTag} not found` } },
|
||||
]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
|
@ -186,8 +186,14 @@ export default function ({ getService }) {
|
|||
})
|
||||
.expect(200);
|
||||
|
||||
expect(apiResponse.removals).to.eql([
|
||||
{ status: 404, result: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found` },
|
||||
expect(apiResponse.results).to.eql([
|
||||
{
|
||||
success: false,
|
||||
error: {
|
||||
code: 404,
|
||||
message: `Beat ${nonExistentBeatId} and tag ${nonExistentTag} not found`,
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
const esResponse = await es.get({
|
||||
|
|
|
@ -31,7 +31,7 @@ export default function ({ getService }) {
|
|||
config: { elasticsearch: { hosts: ['localhost:9200'], username: 'foo' } },
|
||||
},
|
||||
])
|
||||
.expect(201);
|
||||
.expect(200);
|
||||
const esResponse = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
id: `tag:${tagId}`,
|
||||
|
|
|
@ -25,6 +25,7 @@ export default function ({ getService }) {
|
|||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
|
||||
'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' +
|
||||
'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI';
|
||||
|
||||
const version =
|
||||
chance.integer({ min: 1, max: 10 }) +
|
||||
'.' +
|
||||
|
@ -62,9 +63,14 @@ export default function ({ getService }) {
|
|||
await supertest
|
||||
.put(`/api/beats/agent/${beatId}`)
|
||||
.set('kbn-xsrf', 'xxx')
|
||||
.set('kbn-beats-access-token', validEnrollmentToken)
|
||||
.set(
|
||||
'kbn-beats-access-token',
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.' +
|
||||
'eyJjcmVhdGVkIjoiMjAxOC0wNi0zMFQwMzo0MjoxNS4yMzBaIiwiaWF0IjoxNTMwMzMwMTM1fQ.' +
|
||||
'SSsX2Byyo1B1bGxV8C3G4QldhE5iH87EY_1r21-bwbI'
|
||||
)
|
||||
.send(beat)
|
||||
.expect(204);
|
||||
.expect(200);
|
||||
|
||||
const beatInEs = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -88,7 +94,7 @@ export default function ({ getService }) {
|
|||
.send(beat)
|
||||
.expect(401);
|
||||
|
||||
expect(body.message).to.be('Invalid access token');
|
||||
expect(body.error.message).to.be('Invalid access token');
|
||||
|
||||
const beatInEs = await es.get({
|
||||
index: ES_INDEX_NAME,
|
||||
|
@ -111,7 +117,7 @@ export default function ({ getService }) {
|
|||
.send(beat)
|
||||
.expect(404);
|
||||
|
||||
expect(body.message).to.be('Beat not found');
|
||||
expect(body.error.message).to.be('Beat not found');
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue