Skip to content

Commit c312044

Browse files
committed
Support logging HTTP auth denial reason
## What? Support logging (at INFO level) the reason provided by the HTTP auth backend why authentication or autorisation was denied. ## Why? * Security and compliance often require detailed logs of why access was denied * Operational debugging: Operators see immediately in RabbitMQ logs why authentication/authorization failed without checking the HTTP backend logs ## How? The HTTP body returned by the HTTP auth server is allowed to be `deny <Reason>` where `<Reason>` is any text that will be logged by RabbitMQ at INFO level.
1 parent 3c4707d commit c312044

File tree

3 files changed

+45
-21
lines changed

3 files changed

+45
-21
lines changed

deps/rabbitmq_auth_backend_http/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,11 @@ Your web server should always return HTTP 200 OK, with a body
123123
containing:
124124

125125
* `deny`: deny access to the user / vhost / resource
126+
* `deny <Reason>`: deny access to the user / vhost / resource. RabbitMQ will log the `<Reason>` at INFO level.
126127
* `allow`: allow access to the user / vhost / resource
127128
* `allow [list of tags]` (for `user_path` only): allow access, and mark the user as an having the tags listed
128129

129130
## Using TLS/HTTPS
130-
131131
If your Web server uses HTTPS and certificate verification, you need to
132132
configure the plugin to use a CA and client certificate/key pair using the `rabbitmq_auth_backend_http.ssl_options` config variable:
133133

deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,27 @@ description() ->
3434
%%--------------------------------------------------------------------
3535

3636
user_login_authentication(Username, AuthProps) ->
37-
case http_req(p(user_path), q([{username, Username}] ++ extract_other_credentials(AuthProps))) of
38-
{error, _} = E -> E;
39-
"deny" -> {refused, "Denied by the backing HTTP service", []};
40-
"allow" ++ Rest -> Tags = [rabbit_data_coercion:to_atom(T) ||
41-
T <- string:tokens(Rest, " ")],
42-
43-
{ok, #auth_user{username = Username,
44-
tags = Tags,
45-
impl = fun() -> proplists:delete(username, AuthProps) end}};
46-
Other -> {error, {bad_response, Other}}
37+
Path = p(user_path),
38+
Query = q([{username, Username}] ++ extract_other_credentials(AuthProps)),
39+
case http_req(Path, Query) of
40+
{error, _} = Err ->
41+
Err;
42+
"deny " ++ Reason ->
43+
?LOG_INFO("HTTP authentication denied for user '~ts': ~ts",
44+
[Username, Reason]),
45+
{refused, "Denied by the backing HTTP service", []};
46+
Body ->
47+
case string:lowercase(Body) of
48+
"deny" ->
49+
{refused, "Denied by the backing HTTP service", []};
50+
"allow" ++ Rest ->
51+
Tags = [rabbit_data_coercion:to_atom(T)
52+
|| T <- string:tokens(Rest, " ")],
53+
{ok, #auth_user{
54+
username = Username,
55+
tags = Tags,
56+
impl = fun() -> proplists:delete(username, AuthProps) end}}
57+
end
4758
end.
4859

4960
%% When a protocol plugin uses an internal AMQP 0-9-1 client to interact with RabbitMQ core,
@@ -153,13 +164,26 @@ context_as_parameters(_) ->
153164
[].
154165

155166
bool_req(PathName, Props) ->
156-
case http_req(p(PathName), q(Props)) of
157-
"deny" -> false;
158-
"allow" -> true;
159-
E -> E
167+
Path = p(PathName),
168+
Query = q(Props),
169+
case http_req(Path, Query) of
170+
{error, _} = Err ->
171+
Err;
172+
"deny " ++ Reason ->
173+
?LOG_INFO("HTTP authorisation denied for path ~ts with query ~ts: ~ts",
174+
[Path, Query, Reason]),
175+
false;
176+
Body ->
177+
case string:lowercase(Body) of
178+
"deny" ->
179+
false;
180+
"allow" ->
181+
true
182+
end
160183
end.
161184

162-
http_req(Path, Query) -> http_req(Path, Query, ?RETRY_ON_KEEPALIVE_CLOSED).
185+
http_req(Path, Query) ->
186+
http_req(Path, Query, ?RETRY_ON_KEEPALIVE_CLOSED).
163187

164188
http_req(Path, Query, Retry) ->
165189
case do_http_req(Path, Query) of
@@ -204,8 +228,10 @@ do_http_req(Path0, Query) ->
204228
{ok, {{_HTTP, Code, _}, _Headers, Body}} ->
205229
?LOG_DEBUG("auth_backend_http: response code is ~tp, body: ~tp", [Code, Body]),
206230
case lists:member(Code, ?SUCCESSFUL_RESPONSE_CODES) of
207-
true -> parse_resp(Body);
208-
false -> {error, {Code, Body}}
231+
true ->
232+
string:strip(Body);
233+
false ->
234+
{error, {Code, Body}}
209235
end;
210236
{error, _} = E ->
211237
E
@@ -240,8 +266,6 @@ escape(K, Map) when is_map(Map) ->
240266
escape(K, V) ->
241267
rabbit_data_coercion:to_list(K) ++ "=" ++ rabbit_http_util:quote_plus(V).
242268

243-
parse_resp(Resp) -> string:to_lower(string:strip(Resp)).
244-
245269
join_tags([]) -> "";
246270
join_tags(Tags) ->
247271
Strings = [rabbit_data_coercion:to_list(T) || T <- Tags],

deps/rabbitmq_auth_backend_http/test/auth_http_mock.erl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,5 +28,5 @@ authenticate(QsVals, Users) ->
2828
{_OtherPassword, _, _} ->
2929
<<"deny">>;
3030
undefined ->
31-
<<"deny">>
31+
<<"deny unknown_user">>
3232
end.

0 commit comments

Comments
 (0)