Skip to content

Commit c0d0ad0

Browse files
committed
Add exchange checks
1 parent fd4a0b9 commit c0d0ad0

File tree

3 files changed

+159
-84
lines changed

3 files changed

+159
-84
lines changed

deps/rabbit/src/rabbit_amqp_management.erl

Lines changed: 75 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
-export([handle_request/4]).
77

8+
-type resource_name() :: rabbit_types:exchange_name() | rabbit_types:rabbit_amqqueue_name().
9+
810
-spec handle_request(binary(), rabbit_types:vhost(), rabbit_types:user(), pid()) -> iolist().
911
handle_request(Request, Vhost, User, ConnectionPid) ->
1012
ReqSections = amqp10_framing:decode_bin(Request),
@@ -31,7 +33,7 @@ handle_request(Request, Vhost, User, ConnectionPid) ->
3133
Vhost,
3234
User,
3335
ConnectionPid)
34-
catch throw:{StatusCode0, Explanation} ->
36+
catch throw:{?MODULE, StatusCode0, Explanation} ->
3537
{StatusCode0, [], {utf8, unicode:characters_to_binary(Explanation)}}
3638
end,
3739

@@ -52,7 +54,7 @@ handle_request(Request, Vhost, User, ConnectionPid) ->
5254
%% If queue with same fields already exists, return 200 including the queue content.
5355
%% If queue / exchange with other fields exists, return 409 with explanation about which fields diff.
5456
handle_http_req(<<"PUT">>,
55-
[<<"queues">>, QNameBinQ],
57+
[<<"queues">>, QNameBinQuoted],
5658
_Query,
5759
ReqPayload,
5860
Vhost,
@@ -64,7 +66,7 @@ handle_http_req(<<"PUT">>,
6466
arguments := QArgs
6567
} = decode_queue(ReqPayload),
6668
QType = rabbit_amqqueue:get_queue_type(QArgs),
67-
QNameBin = uri_string:unquote(QNameBinQ),
69+
QNameBin = uri_string:unquote(QNameBinQuoted),
6870
QName = rabbit_misc:r(Vhost, queue, QNameBin),
6971
Owner = case Exclusive of
7072
true -> ConnPid;
@@ -76,38 +78,55 @@ handle_http_req(<<"PUT">>,
7678
{<<"201">>, [], null};
7779

7880
handle_http_req(<<"PUT">>,
79-
[<<"exchanges">>, XNameBinQ],
81+
[<<"exchanges">>, XNameBinQuoted],
8082
_Query,
8183
ReqPayload,
8284
Vhost,
83-
#user{username = Username},
85+
User = #user{username = Username},
8486
_ConnPid) ->
85-
#{type := XType,
87+
XNameBin = uri_string:unquote(XNameBinQuoted),
88+
#{type := XTypeBin,
8689
durable := Durable,
8790
auto_delete := AutoDelete,
8891
internal := Internal,
8992
arguments := XArgs
9093
} = decode_exchange(ReqPayload),
91-
XNameBin = uri_string:unquote(XNameBinQ),
94+
XTypeAtom = try rabbit_exchange:check_type(XTypeBin)
95+
catch exit:#amqp_error{explanation = Explanation} ->
96+
throw(<<"400">>, Explanation, [])
97+
end,
98+
ok = prohibit_default_exchange(XNameBin),
9299
XName = rabbit_misc:r(Vhost, exchange, XNameBin),
93-
CheckedXType = rabbit_exchange:check_type(XType),
94-
_X = rabbit_exchange:declare(XName,
95-
CheckedXType,
96-
Durable,
97-
AutoDelete,
98-
Internal,
99-
XArgs,
100-
Username),
101-
{<<"201">>, [], null};
100+
ok = check_resource_access(XName, configure, User),
101+
{StatCode, X} = case rabbit_exchange:lookup(XName) of
102+
{ok, FoundX} ->
103+
{<<"200">>, FoundX};
104+
{error, not_found} ->
105+
ok = prohibit_cr_lf(XNameBin),
106+
ok = prohibit_reserved_amq(XName),
107+
X0 = rabbit_exchange:declare(
108+
XName, XTypeAtom, Durable, AutoDelete,
109+
Internal, XArgs, Username),
110+
{<<"201">>, X0}
111+
end,
112+
try rabbit_exchange:assert_equivalence(
113+
X, XTypeAtom, Durable, AutoDelete, Internal, XArgs) of
114+
ok ->
115+
%%TODO Include the exchange in the response payload
116+
{StatCode, [], null}
117+
catch exit:#amqp_error{name = precondition_failed,
118+
explanation = Expl} ->
119+
throw(<<"409">>, Expl, [])
120+
end;
102121

103122
handle_http_req(<<"DELETE">>,
104-
[<<"queues">>, QNameBinQ, <<"messages">>],
123+
[<<"queues">>, QNameBinQuoted, <<"messages">>],
105124
_Query,
106125
null,
107126
Vhost,
108127
_User,
109128
ConnPid) ->
110-
QNameBin = uri_string:unquote(QNameBinQ),
129+
QNameBin = uri_string:unquote(QNameBinQuoted),
111130
QName = rabbit_misc:r(Vhost, queue, QNameBin),
112131
{ok, NumMsgs} = rabbit_amqqueue:with_exclusive_access_or_die(
113132
QName, ConnPid,
@@ -118,13 +137,13 @@ handle_http_req(<<"DELETE">>,
118137
{<<"200">>, [], RespPayload};
119138

120139
handle_http_req(<<"DELETE">>,
121-
[<<"queues">>, QNameBinQ],
140+
[<<"queues">>, QNameBinQuoted],
122141
_Query,
123142
null,
124143
Vhost,
125144
#user{username = Username},
126145
ConnPid) ->
127-
QNameBin = uri_string:unquote(QNameBinQ),
146+
QNameBin = uri_string:unquote(QNameBinQuoted),
128147
QName = rabbit_misc:r(Vhost, queue, QNameBin),
129148
{ok, NumMsgs} = rabbit_amqqueue:delete_with(QName, ConnPid, false, false, Username, true),
130149
RespPayload = {map, [{{utf8, <<"message_count">>}, {ulong, NumMsgs}}]},
@@ -414,8 +433,43 @@ args_hash(Args)
414433
base64:encode(Bin, #{mode => urlsafe,
415434
padding => false}).
416435

436+
prohibit_cr_lf(NameBin) ->
437+
case binary:match(NameBin, [<<"\n">>, <<"\r">>]) of
438+
nomatch ->
439+
ok;
440+
_Found ->
441+
throw(<<"400">>, <<"Bad name '~ts': \n and \r not allowed">>, [NameBin])
442+
end.
443+
444+
prohibit_default_exchange(<<>>) ->
445+
throw(<<"403">>, <<"operation not permitted on the default exchange">>, []);
446+
prohibit_default_exchange(_) ->
447+
ok.
448+
449+
-spec prohibit_reserved_amq(resource_name()) -> ok.
450+
prohibit_reserved_amq(Res = #resource{name = <<"amq.", _/binary>>}) ->
451+
throw(<<"403">>,
452+
"~ts starts with reserved prefix 'amq.'",
453+
[rabbit_misc:rs(Res)]);
454+
prohibit_reserved_amq(#resource{}) ->
455+
ok.
456+
457+
-spec check_resource_access(resource_name(),
458+
rabbit_types:permission_atom(),
459+
rabbit_types:user()) -> ok.
460+
check_resource_access(Resource, Perm, User) ->
461+
try rabbit_access_control:check_resource_access(User, Resource, Perm, #{})
462+
catch exit:#amqp_error{name = not_allowed} ->
463+
%% For authorization failures, let's be more strict: Close the entire
464+
%% AMQP session instead of only returning a HTTP Status Code 403.
465+
rabbit_amqp_util:protocol_error(
466+
?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS,
467+
"~s access refused for user '~ts' to ~ts",
468+
[Perm, User, rabbit_misc:rs(Resource)])
469+
end.
470+
417471
-spec throw(binary(), io:format(), [term()]) -> no_return().
418472
throw(StatusCode, Format, Data) ->
419473
Explanation = lists:flatten(io_lib:format(Format, Data)),
420474
rabbit_log:warning(Explanation),
421-
throw({StatusCode, Explanation}).
475+
throw({?MODULE, StatusCode, Explanation}).

deps/rabbit/src/rabbit_channel.erl

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2553,7 +2553,7 @@ handle_method(#'queue.purge'{queue = QueueNameBin},
25532553
[rabbit_misc:rs(amqqueue:get_name(Q))])
25542554
end
25552555
end);
2556-
handle_method(#'exchange.declare'{exchange = ExchangeNameBin,
2556+
handle_method(#'exchange.declare'{exchange = XNameBin,
25572557
type = TypeNameBin,
25582558
passive = false,
25592559
durable = Durable,
@@ -2563,13 +2563,14 @@ handle_method(#'exchange.declare'{exchange = ExchangeNameBin,
25632563
_ConnPid, AuthzContext, _CollectorPid, VHostPath,
25642564
#user{username = Username} = User) ->
25652565
CheckedType = rabbit_exchange:check_type(TypeNameBin),
2566-
ExchangeName = rabbit_misc:r(VHostPath, exchange, strip_cr_lf(ExchangeNameBin)),
2566+
XNameBinStripped = strip_cr_lf(XNameBin),
2567+
ExchangeName = rabbit_misc:r(VHostPath, exchange, XNameBinStripped),
25672568
check_not_default_exchange(ExchangeName),
25682569
check_configure_permitted(ExchangeName, User, AuthzContext),
25692570
X = case rabbit_exchange:lookup(ExchangeName) of
25702571
{ok, FoundX} -> FoundX;
25712572
{error, not_found} ->
2572-
_ = check_name('exchange', strip_cr_lf(ExchangeNameBin)),
2573+
_ = check_name('exchange', XNameBinStripped),
25732574
AeKey = <<"alternate-exchange">>,
25742575
case rabbit_misc:r_arg(VHostPath, exchange, Args, AeKey) of
25752576
undefined -> ok;

0 commit comments

Comments
 (0)