Merge pull request #9708 from rabbitmq/mk-limit-max-http-api-payload-size

Introduce a configurable limit to HTTP API request body size
This commit is contained in:
Michael Klishin 2023-10-16 21:49:50 -04:00 committed by GitHub
commit 6009a4973f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 65 additions and 11 deletions

View File

@ -35,7 +35,8 @@ APP_ENV = """[
{cors_allow_origins, []},
{cors_max_age, 1800},
{content_security_policy, "script-src 'self' 'unsafe-eval' 'unsafe-inline'; object-src 'self'"}
{content_security_policy, "script-src 'self' 'unsafe-eval' 'unsafe-inline'; object-src 'self'"},
{max_http_body_size, 10000000}
]"""
genrule(

View File

@ -12,7 +12,8 @@ define PROJECT_ENV
{cors_allow_origins, []},
{cors_max_age, 1800},
{content_security_policy, "script-src 'self' 'unsafe-eval' 'unsafe-inline'; object-src 'self'"}
{content_security_policy, "script-src 'self' 'unsafe-eval' 'unsafe-inline'; object-src 'self'"},
{max_http_body_size, 10000000}
]
endef

View File

@ -11,3 +11,5 @@
-define(MANAGEMENT_PG_SCOPE, rabbitmq_management).
-define(MANAGEMENT_PG_GROUP, management_db).
-define(MANAGEMENT_DEFAULT_HTTP_MAX_BODY_SIZE, 20000000).

View File

@ -20,6 +20,23 @@
{mapping, "management.http_log_dir", "rabbitmq_management.http_log_dir",
[{datatype, string}]}.
%% Max HTTP body limit
{mapping, "management.http.max_body_size", "rabbitmq_management.max_http_body_size",
[{datatype, integer}, {validators, ["non_negative_integer"]}]}.
{translation, "rabbitmq_management.max_http_body_size",
fun(Conf) ->
case cuttlefish:conf_get("management.http.max_body_size", Conf, undefined) of
%% 10 MiB allows for about 100K queues with short names across a small (single digit) number of virtual hosts with
%% an equally small number of users. MK.
undefined -> 10000000;
Val when is_integer(Val) -> Val;
Other -> cuttlefish:invalid("management.http.max_body_size must be set to a positive integer")
end
end}.
%% HTTP (TCP) listener options ========================================================
%% HTTP listener consistent with Web STOMP and Web MQTT.

View File

@ -686,15 +686,27 @@ id(Key, ReqData) ->
read_complete_body(Req) ->
read_complete_body(Req, <<"">>).
read_complete_body(Req0, Acc) ->
case cowboy_req:read_body(Req0) of
{ok, Data, Req} -> {ok, <<Acc/binary, Data/binary>>, Req};
{more, Data, Req} -> read_complete_body(Req, <<Acc/binary, Data/binary>>)
read_complete_body(Req, Acc) ->
BodySizeLimit = application:get_env(rabbitmq_management, max_http_body_size, ?MANAGEMENT_DEFAULT_HTTP_MAX_BODY_SIZE),
read_complete_body(Req, Acc, BodySizeLimit).
read_complete_body(Req0, Acc, BodySizeLimit) ->
case bit_size(Acc) > BodySizeLimit of
true ->
{error, "Exceeded HTTP request body size limit"};
false ->
case cowboy_req:read_body(Req0) of
{ok, Data, Req} -> {ok, <<Acc/binary, Data/binary>>, Req};
{more, Data, Req} -> read_complete_body(Req, <<Acc/binary, Data/binary>>)
end
end.
with_decode(Keys, ReqData, Context, Fun) ->
{ok, Body, ReqData1} = read_complete_body(ReqData),
with_decode(Keys, Body, ReqData1, Context, Fun).
case read_complete_body(ReqData) of
{error, Reason} ->
bad_request(Reason, ReqData, Context);
{ok, Body, ReqData1} ->
with_decode(Keys, Body, ReqData1, Context, Fun)
end.
with_decode(Keys, Body, ReqData, Context, Fun) ->
case decode(Keys, Body) of

View File

@ -84,8 +84,15 @@ all_definitions(ReqData, Context) ->
Context).
accept_json(ReqData0, Context) ->
{ok, Body, ReqData} = rabbit_mgmt_util:read_complete_body(ReqData0),
accept(Body, ReqData, Context).
case rabbit_mgmt_util:read_complete_body(ReqData0) of
{error, Reason} ->
BodySizeLimit = application:get_env(rabbitmq_management, max_http_body_size, ?MANAGEMENT_DEFAULT_HTTP_MAX_BODY_SIZE),
_ = rabbit_log:warning("HTTP API: uploaded definition file exceeded the maximum request body limit of ~p bytes. "
"Use the 'management.http.max_body_size' key in rabbitmq.conf to increase the limit if necessary", [BodySizeLimit]),
rabbit_mgmt_util:bad_request(Reason, ReqData0, Context);
{ok, Body, ReqData} ->
accept(Body, ReqData, Context)
end.
vhost_definitions(ReqData, VHost, Context) ->
%% rabbit_mgmt_wm_<>:basic/1 filters by VHost if it is available

View File

@ -126,6 +126,7 @@ all_tests() -> [
get_fail_test,
publish_test,
publish_large_message_test,
publish_large_message_exceeding_http_request_body_size_test,
publish_accept_json_test,
publish_fail_test,
publish_base64_test,
@ -2644,7 +2645,7 @@ get_fail_test(Config) ->
passed.
-define(LARGE_BODY_BYTES, 25000000).
-define(LARGE_BODY_BYTES, 5000000).
publish_test(Config) ->
Headers = #{'x-forwarding' => [#{uri => <<"amqp://localhost/%2F/upstream">>}]},
@ -2687,6 +2688,19 @@ publish_large_message_test(Config) ->
http_delete(Config, "/queues/%2F/publish_accept_json_test", {group, '2xx'}),
passed.
-define(EXCESSIVELY_LARGE_BODY_BYTES, 35000000).
publish_large_message_exceeding_http_request_body_size_test(Config) ->
Headers = #{'x-forwarding' => [#{uri => <<"amqp://localhost/%2F/upstream">>}]},
Body = binary:copy(<<"a">>, ?EXCESSIVELY_LARGE_BODY_BYTES),
Msg = msg(<<"large_message_exceeding_http_request_body_size_test">>, Headers, Body),
http_put(Config, "/queues/%2F/large_message_exceeding_http_request_body_size_test", #{}, {group, '2xx'}),
%% exceeds the default HTTP API request body size limit
http_post_accept_json(Config, "/exchanges/%2F/amq.default/publish",
Msg, ?BAD_REQUEST),
http_delete(Config, "/queues/%2F/large_message_exceeding_http_request_body_size_test", {group, '2xx'}),
passed.
publish_accept_json_test(Config) ->
Headers = #{'x-forwarding' => [#{uri => <<"amqp://localhost/%2F/upstream">>}]},
Msg = msg(<<"publish_accept_json_test">>, Headers, <<"Hello world">>),