Add http.payloadTimeout config option (#177309)

## Summary

Fix https://github.com/elastic/kibana/issues/177138

- Add a `http.payloadTimeout` configuration option, to control the
payload timeout
- Set the default value for this option to `20s` (was `10s` previously)
This commit is contained in:
Pierre Gayvallet 2024-02-22 12:33:49 +01:00 committed by GitHub
parent 9a473879af
commit 38a3b9675d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 46 additions and 2 deletions

View file

@ -467,6 +467,11 @@ default is `true`. *Default: `deprecated`*
The number of milliseconds to wait before closing an The number of milliseconds to wait before closing an
inactive socket. *Default: `"120000"`* inactive socket. *Default: `"120000"`*
[[server-payloadTimeout]] `server.payloadTimeout`::
Sets the maximum time allowed for the client to transmit the request payload (body) before giving up
and responding with a Request Timeout (408) error response.
*Default: `"20000"`*
[[server-ssl-cert-key]] `server.ssl.certificate` and `server.ssl.key`:: [[server-ssl-cert-key]] `server.ssl.certificate` and `server.ssl.key`::
Paths to a PEM-encoded X.509 server certificate and its corresponding private key. These Paths to a PEM-encoded X.509 server certificate and its corresponding private key. These
are used by {kib} to establish trust when receiving inbound SSL/TLS connections from users. are used by {kib} to establish trust when receiving inbound SSL/TLS connections from users.

View file

@ -73,6 +73,7 @@ Object {
"valueInBytes": 1048576, "valueInBytes": 1048576,
}, },
"name": "kibana-hostname", "name": "kibana-hostname",
"payloadTimeout": 20000,
"port": 5601, "port": 5601,
"requestId": Object { "requestId": Object {
"allowFromAnyIp": false, "allowFromAnyIp": false,

View file

@ -349,6 +349,14 @@ test('can specify socket timeouts', () => {
expect(socketTimeout).toBe(5e5); expect(socketTimeout).toBe(5e5);
}); });
test('can specify payload timeouts', () => {
const obj = {
payloadTimeout: 654321,
};
const { payloadTimeout } = config.schema.validate(obj);
expect(payloadTimeout).toBe(654321);
});
describe('with compression', () => { describe('with compression', () => {
test('accepts valid referrer whitelist', () => { test('accepts valid referrer whitelist', () => {
const { const {

View file

@ -26,6 +26,8 @@ import {
} from './security_response_headers_config'; } from './security_response_headers_config';
import { CdnConfig } from './cdn_config'; import { CdnConfig } from './cdn_config';
const SECOND = 1000;
const validBasePathRegex = /^\/.*[^\/]$/; const validBasePathRegex = /^\/.*[^\/]$/;
const hostURISchema = schema.uri({ scheme: ['http', 'https'] }); const hostURISchema = schema.uri({ scheme: ['http', 'https'] });
@ -129,10 +131,13 @@ const configSchema = schema.object(
rewriteBasePath: schema.boolean({ defaultValue: false }), rewriteBasePath: schema.boolean({ defaultValue: false }),
ssl: sslSchema, ssl: sslSchema,
keepaliveTimeout: schema.number({ keepaliveTimeout: schema.number({
defaultValue: 120000, defaultValue: 120 * SECOND,
}), }),
socketTimeout: schema.number({ socketTimeout: schema.number({
defaultValue: 120000, defaultValue: 120 * SECOND,
}),
payloadTimeout: schema.number({
defaultValue: 20 * SECOND,
}), }),
compression: schema.object({ compression: schema.object({
enabled: schema.boolean({ defaultValue: true }), enabled: schema.boolean({ defaultValue: true }),
@ -278,6 +283,7 @@ export class HttpConfig implements IHttpConfig {
public host: string; public host: string;
public keepaliveTimeout: number; public keepaliveTimeout: number;
public socketTimeout: number; public socketTimeout: number;
public payloadTimeout: number;
public port: number; public port: number;
public cors: { public cors: {
enabled: boolean; enabled: boolean;
@ -342,6 +348,7 @@ export class HttpConfig implements IHttpConfig {
this.publicBaseUrl = rawHttpConfig.publicBaseUrl; this.publicBaseUrl = rawHttpConfig.publicBaseUrl;
this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout; this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout;
this.socketTimeout = rawHttpConfig.socketTimeout; this.socketTimeout = rawHttpConfig.socketTimeout;
this.payloadTimeout = rawHttpConfig.payloadTimeout;
this.rewriteBasePath = rawHttpConfig.rewriteBasePath; this.rewriteBasePath = rawHttpConfig.rewriteBasePath;
this.ssl = new SslConfig(rawHttpConfig.ssl || {}); this.ssl = new SslConfig(rawHttpConfig.ssl || {});
this.compression = rawHttpConfig.compression; this.compression = rawHttpConfig.compression;

View file

@ -30,6 +30,9 @@ export const httpConfigSchema = schema.object(
socketTimeout: schema.number({ socketTimeout: schema.number({
defaultValue: 120000, defaultValue: 120000,
}), }),
payloadTimeout: schema.number({
defaultValue: 20000,
}),
cors: schema.object({ cors: schema.object({
enabled: schema.boolean({ defaultValue: false }), enabled: schema.boolean({ defaultValue: false }),
allowCredentials: schema.boolean({ defaultValue: false }), allowCredentials: schema.boolean({ defaultValue: false }),
@ -53,6 +56,7 @@ export class HttpConfig implements IHttpConfig {
shutdownTimeout: Duration; shutdownTimeout: Duration;
keepaliveTimeout: number; keepaliveTimeout: number;
socketTimeout: number; socketTimeout: number;
payloadTimeout: number;
cors: ICorsConfig; cors: ICorsConfig;
ssl: ISslConfig; ssl: ISslConfig;
restrictInternalApis: boolean; restrictInternalApis: boolean;
@ -64,6 +68,7 @@ export class HttpConfig implements IHttpConfig {
this.maxPayload = rawConfig.maxPayload; this.maxPayload = rawConfig.maxPayload;
this.shutdownTimeout = rawConfig.shutdownTimeout; this.shutdownTimeout = rawConfig.shutdownTimeout;
this.keepaliveTimeout = rawConfig.keepaliveTimeout; this.keepaliveTimeout = rawConfig.keepaliveTimeout;
this.payloadTimeout = rawConfig.payloadTimeout;
this.socketTimeout = rawConfig.socketTimeout; this.socketTimeout = rawConfig.socketTimeout;
this.cors = rawConfig.cors; this.cors = rawConfig.cors;
this.ssl = new SslConfig(rawConfig.ssl); this.ssl = new SslConfig(rawConfig.ssl);

View file

@ -39,6 +39,7 @@ describe('BasePathProxyServer', () => {
shutdownTimeout: moment.duration(30, 'seconds'), shutdownTimeout: moment.duration(30, 'seconds'),
keepaliveTimeout: 1000, keepaliveTimeout: 1000,
socketTimeout: 1000, socketTimeout: 1000,
payloadTimeout: 1000,
cors: { cors: {
enabled: false, enabled: false,
allowCredentials: false, allowCredentials: false,

View file

@ -19,6 +19,7 @@ describe('server config', () => {
"maxPayload": ByteSizeValue { "maxPayload": ByteSizeValue {
"valueInBytes": 1048576, "valueInBytes": 1048576,
}, },
"payloadTimeout": 20000,
"port": 3000, "port": 3000,
"restrictInternalApis": false, "restrictInternalApis": false,
"shutdownTimeout": "PT30S", "shutdownTimeout": "PT30S",

View file

@ -27,6 +27,9 @@ const configSchema = schema.object(
keepaliveTimeout: schema.number({ keepaliveTimeout: schema.number({
defaultValue: 120000, defaultValue: 120000,
}), }),
payloadTimeout: schema.number({
defaultValue: 20000,
}),
shutdownTimeout: schema.duration({ shutdownTimeout: schema.duration({
defaultValue: '30s', defaultValue: '30s',
validate: (duration) => { validate: (duration) => {
@ -73,6 +76,7 @@ export class ServerConfig implements IHttpConfig {
keepaliveTimeout: number; keepaliveTimeout: number;
shutdownTimeout: Duration; shutdownTimeout: Duration;
socketTimeout: number; socketTimeout: number;
payloadTimeout: number;
ssl: ISslConfig; ssl: ISslConfig;
cors: ICorsConfig; cors: ICorsConfig;
restrictInternalApis: boolean; restrictInternalApis: boolean;
@ -84,6 +88,7 @@ export class ServerConfig implements IHttpConfig {
this.keepaliveTimeout = rawConfig.keepaliveTimeout; this.keepaliveTimeout = rawConfig.keepaliveTimeout;
this.shutdownTimeout = rawConfig.shutdownTimeout; this.shutdownTimeout = rawConfig.shutdownTimeout;
this.socketTimeout = rawConfig.socketTimeout; this.socketTimeout = rawConfig.socketTimeout;
this.payloadTimeout = rawConfig.payloadTimeout;
this.ssl = new SslConfig(rawConfig.ssl); this.ssl = new SslConfig(rawConfig.ssl);
this.cors = { this.cors = {
enabled: false, enabled: false,

View file

@ -25,6 +25,7 @@ const createConfig = (parts: Partial<IHttpConfig>): IHttpConfig => ({
port: 5601, port: 5601,
socketTimeout: 120000, socketTimeout: 120000,
keepaliveTimeout: 120000, keepaliveTimeout: 120000,
payloadTimeout: 20000,
shutdownTimeout: moment.duration(30, 'seconds'), shutdownTimeout: moment.duration(30, 'seconds'),
maxPayload: ByteSizeValue.parse('1048576b'), maxPayload: ByteSizeValue.parse('1048576b'),
...parts, ...parts,
@ -122,4 +123,12 @@ describe('getServerOptions', () => {
headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'], headers: ['Accept', 'Authorization', 'Content-Type', 'If-None-Match', 'kbn-xsrf'],
}); });
}); });
it('properly configures `routes.payload.timeout`', () => {
const httpConfig = createConfig({
payloadTimeout: 9007,
});
expect(getServerOptions(httpConfig).routes!.payload!.timeout).toEqual(9007);
});
}); });

View file

@ -35,6 +35,7 @@ export function getServerOptions(config: IHttpConfig, { configureTLS = true } =
cors, cors,
payload: { payload: {
maxBytes: config.maxPayload.getValueInBytes(), maxBytes: config.maxPayload.getValueInBytes(),
timeout: config.payloadTimeout,
}, },
validate: { validate: {
failAction: defaultValidationErrorHandler, failAction: defaultValidationErrorHandler,

View file

@ -15,6 +15,7 @@ export interface IHttpConfig {
maxPayload: ByteSizeValue; maxPayload: ByteSizeValue;
keepaliveTimeout: number; keepaliveTimeout: number;
socketTimeout: number; socketTimeout: number;
payloadTimeout: number;
cors: ICorsConfig; cors: ICorsConfig;
ssl: ISslConfig; ssl: ISslConfig;
shutdownTimeout: Duration; shutdownTimeout: Duration;