Skip to content

Commit 0e01401

Browse files
mibragimovMuhammad Ibragimov
andauthored
[Console] Fix autocomplete_entities API crash when response size is too big (elastic#140569)
Fixes elastic#144310 ### Summary This PR addresses the issue of the Kibana instance restarting when the response size is too big for the `autocomplete_entities` API. This happens when a cluster has a large number of mappings and we try to retrieve them all on the server side with `esClient.asInternalUser.indices.getMapping()`. esClient does not handle large responses well and throws an error that causes the Kibana instance to restart. As node's max [string length](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length#description) is 2^28-1 (~512MB) if the response size is over 512MB, it will throw an error with the message "Invalid string length". The fix is to use the raw http request to retrieve the mappings instead of esClient and check the response size before sending it to the client. If the response size is too big, we will return an empty object and log the error in the server logs. #### Proposed changes - Remove ES JS client requests and use native Node.js HTTP client instead - Limit the response size to 10MB for the `autocomplete_entities` API #### How to test this PR locally To test this out, you will need to connect Kibana to a remote cluster with a large number of mappings. We created a patch file that you can apply to your local Kibana instance to test this PR. Since the patch file contains credentials, we can't share it publicly. Please reach out to me if you would like to test this PR. I will share the patch file and the instructions to apply it. Co-authored-by: Muhammad Ibragimov <muhammad.ibragimov@elastic.co>
1 parent d077a51 commit 0e01401

20 files changed

+542
-353
lines changed

src/plugins/console/public/lib/autocomplete/components/component_template_autocomplete_component.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { getAutocompleteInfo } from '../../../services';
9+
import { getAutocompleteInfo, ENTITIES } from '../../../services';
1010
import { ListComponent } from './list_component';
1111

1212
export class ComponentTemplateAutocompleteComponent extends ListComponent {
1313
constructor(name, parent) {
14-
super(name, getAutocompleteInfo().getEntityProvider('componentTemplates'), parent, true, true);
14+
super(
15+
name,
16+
getAutocompleteInfo().getEntityProvider(ENTITIES.COMPONENT_TEMPLATES),
17+
parent,
18+
true,
19+
true
20+
);
1521
}
1622

1723
getContextKey() {

src/plugins/console/public/lib/autocomplete/components/data_stream_autocomplete_component.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,16 @@
77
*/
88

99
import { ListComponent } from './list_component';
10-
import { getAutocompleteInfo } from '../../../services';
10+
import { getAutocompleteInfo, ENTITIES } from '../../../services';
1111

1212
export class DataStreamAutocompleteComponent extends ListComponent {
1313
constructor(name, parent, multiValued) {
14-
super(name, getAutocompleteInfo().getEntityProvider('dataStreams'), parent, multiValued);
14+
super(
15+
name,
16+
getAutocompleteInfo().getEntityProvider(ENTITIES.DATA_STREAMS),
17+
parent,
18+
multiValued
19+
);
1520
}
1621

1722
getContextKey() {

src/plugins/console/public/lib/autocomplete/components/field_autocomplete_component.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@
77
*/
88

99
import _ from 'lodash';
10-
import { getAutocompleteInfo } from '../../../services';
10+
import { getAutocompleteInfo, ENTITIES } from '../../../services';
1111
import { ListComponent } from './list_component';
1212

1313
function FieldGenerator(context) {
14-
return _.map(getAutocompleteInfo().getEntityProvider('fields', context), function (field) {
14+
return _.map(getAutocompleteInfo().getEntityProvider(ENTITIES.FIELDS, context), function (field) {
1515
return { name: field.name, meta: field.type };
1616
});
1717
}

src/plugins/console/public/lib/autocomplete/components/index_autocomplete_component.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import _ from 'lodash';
10-
import { getAutocompleteInfo } from '../../../services';
10+
import { getAutocompleteInfo, ENTITIES } from '../../../services';
1111
import { ListComponent } from './list_component';
1212

1313
function nonValidIndexType(token) {
@@ -16,7 +16,7 @@ function nonValidIndexType(token) {
1616

1717
export class IndexAutocompleteComponent extends ListComponent {
1818
constructor(name, parent, multiValued) {
19-
super(name, getAutocompleteInfo().getEntityProvider('indices'), parent, multiValued);
19+
super(name, getAutocompleteInfo().getEntityProvider(ENTITIES.INDICES), parent, multiValued);
2020
}
2121
validateTokens(tokens) {
2222
if (!this.multiValued && tokens.length > 1) {

src/plugins/console/public/lib/autocomplete/components/index_template_autocomplete_component.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,18 @@
66
* Side Public License, v 1.
77
*/
88

9-
import { getAutocompleteInfo } from '../../../services';
9+
import { getAutocompleteInfo, ENTITIES } from '../../../services';
1010
import { ListComponent } from './list_component';
1111

1212
export class IndexTemplateAutocompleteComponent extends ListComponent {
1313
constructor(name, parent) {
14-
super(name, getAutocompleteInfo().getEntityProvider('indexTemplates'), parent, true, true);
14+
super(
15+
name,
16+
getAutocompleteInfo().getEntityProvider(ENTITIES.INDEX_TEMPLATES),
17+
parent,
18+
true,
19+
true
20+
);
1521
}
1622

1723
getContextKey() {

src/plugins/console/public/lib/autocomplete/components/username_autocomplete_component.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import _ from 'lodash';
10-
import { getAutocompleteInfo } from '../../../services';
10+
import { getAutocompleteInfo, ENTITIES } from '../../../services';
1111
import { ListComponent } from './list_component';
1212

1313
function nonValidUsernameType(token) {
@@ -16,7 +16,7 @@ function nonValidUsernameType(token) {
1616

1717
export class UsernameAutocompleteComponent extends ListComponent {
1818
constructor(name, parent, multiValued) {
19-
super(name, getAutocompleteInfo().getEntityProvider('indices'), parent, multiValued);
19+
super(name, getAutocompleteInfo().getEntityProvider(ENTITIES.INDICES), parent, multiValued);
2020
}
2121
validateTokens(tokens) {
2222
if (!this.multiValued && tokens.length > 1) {

src/plugins/console/public/lib/autocomplete_entities/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ export interface FieldMapping {
2929
fields?: FieldMapping[];
3030
}
3131

32-
export interface MappingsApiResponse {
32+
export interface AutoCompleteEntitiesApiResponse {
3333
mappings: IndicesGetMappingResponse;
3434
aliases: IndicesGetAliasResponse;
3535
dataStreams: IndicesGetDataStreamResponse;

src/plugins/console/public/services/autocomplete.ts

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import { createGetterSetter } from '@kbn/kibana-utils-plugin/public';
1010
import type { HttpSetup } from '@kbn/core/public';
11-
import type { MappingsApiResponse } from '../lib/autocomplete_entities/types';
11+
import type { AutoCompleteEntitiesApiResponse } from '../lib/autocomplete_entities/types';
1212
import { API_BASE_PATH } from '../../common/constants';
1313
import {
1414
Alias,
@@ -20,6 +20,15 @@ import {
2020
} from '../lib/autocomplete_entities';
2121
import { DevToolsSettings, Settings } from './settings';
2222

23+
export enum ENTITIES {
24+
INDICES = 'indices',
25+
FIELDS = 'fields',
26+
INDEX_TEMPLATES = 'indexTemplates',
27+
COMPONENT_TEMPLATES = 'componentTemplates',
28+
LEGACY_TEMPLATES = 'legacyTemplates',
29+
DATA_STREAMS = 'dataStreams',
30+
}
31+
2332
export class AutocompleteInfo {
2433
public readonly alias = new Alias();
2534
public readonly mapping = new Mapping();
@@ -39,19 +48,19 @@ export class AutocompleteInfo {
3948
context: { indices: string[]; types: string[] } = { indices: [], types: [] }
4049
) {
4150
switch (type) {
42-
case 'indices':
51+
case ENTITIES.INDICES:
4352
const includeAliases = true;
4453
const collaborator = this.mapping;
4554
return () => this.alias.getIndices(includeAliases, collaborator);
46-
case 'fields':
55+
case ENTITIES.FIELDS:
4756
return this.mapping.getMappings(context.indices, context.types);
48-
case 'indexTemplates':
57+
case ENTITIES.INDEX_TEMPLATES:
4958
return () => this.indexTemplate.getTemplates();
50-
case 'componentTemplates':
59+
case ENTITIES.COMPONENT_TEMPLATES:
5160
return () => this.componentTemplate.getTemplates();
52-
case 'legacyTemplates':
61+
case ENTITIES.LEGACY_TEMPLATES:
5362
return () => this.legacyTemplate.getTemplates();
54-
case 'dataStreams':
63+
case ENTITIES.DATA_STREAMS:
5564
return () => this.dataStream.getDataStreams();
5665
default:
5766
throw new Error(`Unsupported type: ${type}`);
@@ -61,7 +70,7 @@ export class AutocompleteInfo {
6170
public retrieve(settings: Settings, settingsToRetrieve: DevToolsSettings['autocomplete']) {
6271
this.clearSubscriptions();
6372
this.http
64-
.get<MappingsApiResponse>(`${API_BASE_PATH}/autocomplete_entities`, {
73+
.get<AutoCompleteEntitiesApiResponse>(`${API_BASE_PATH}/autocomplete_entities`, {
6574
query: { ...settingsToRetrieve },
6675
})
6776
.then((data) => {
@@ -83,7 +92,7 @@ export class AutocompleteInfo {
8392
}
8493
}
8594

86-
private load(data: MappingsApiResponse) {
95+
private load(data: AutoCompleteEntitiesApiResponse) {
8796
this.mapping.loadMappings(data.mappings);
8897
const collaborator = this.mapping;
8998
this.alias.loadAliases(data.aliases, collaborator);

src/plugins/console/public/services/index.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,9 @@ export { createHistory, History } from './history';
1010
export { createStorage, Storage, StorageKeys, setStorage, getStorage } from './storage';
1111
export type { DevToolsSettings } from './settings';
1212
export { createSettings, Settings, DEFAULT_SETTINGS } from './settings';
13-
export { AutocompleteInfo, getAutocompleteInfo, setAutocompleteInfo } from './autocomplete';
13+
export {
14+
AutocompleteInfo,
15+
getAutocompleteInfo,
16+
setAutocompleteInfo,
17+
ENTITIES,
18+
} from './autocomplete';

src/plugins/console/server/lib/proxy_request.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import net from 'net';
1212
import stream from 'stream';
1313
import Boom from '@hapi/boom';
1414
import { URL } from 'url';
15+
import { sanitizeHostname } from './utils';
1516

1617
interface Args {
1718
method: 'get' | 'post' | 'put' | 'delete' | 'patch' | 'head';
@@ -23,13 +24,6 @@ interface Args {
2324
rejectUnauthorized?: boolean;
2425
}
2526

26-
/**
27-
* Node http request library does not expect there to be trailing "[" or "]"
28-
* characters in ipv6 host names.
29-
*/
30-
const sanitizeHostname = (hostName: string): string =>
31-
hostName.trim().replace(/^\[/, '').replace(/\]$/, '');
32-
3327
// We use a modified version of Hapi's Wreck because Hapi, Axios, and Superagent don't support GET requests
3428
// with bodies, but ES APIs do. Similarly with DELETE requests with bodies. Another library, `request`
3529
// diverged too much from current behaviour.

0 commit comments

Comments
 (0)