From abd856875a1461c0de9c35b06f8cb122def6e5fd Mon Sep 17 00:00:00 2001 From: David Ansari Date: Mon, 29 Sep 2025 17:40:28 +0200 Subject: [PATCH 1/2] 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 ` where `` is any text that will be logged by RabbitMQ at INFO level. --- deps/rabbitmq_auth_backend_http/README.md | 2 +- .../src/rabbit_auth_backend_http.erl | 62 +++++++++++++------ .../test/auth_http_mock.erl | 2 +- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/deps/rabbitmq_auth_backend_http/README.md b/deps/rabbitmq_auth_backend_http/README.md index fefb2889d862..6f34ab9290b4 100644 --- a/deps/rabbitmq_auth_backend_http/README.md +++ b/deps/rabbitmq_auth_backend_http/README.md @@ -123,11 +123,11 @@ Your web server should always return HTTP 200 OK, with a body containing: * `deny`: deny access to the user / vhost / resource +* `deny `: deny access to the user / vhost / resource. RabbitMQ will log the `` at INFO level. * `allow`: allow access to the user / vhost / resource * `allow [list of tags]` (for `user_path` only): allow access, and mark the user as an having the tags listed ## Using TLS/HTTPS - If your Web server uses HTTPS and certificate verification, you need to configure the plugin to use a CA and client certificate/key pair using the `rabbitmq_auth_backend_http.ssl_options` config variable: diff --git a/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl b/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl index 4b5d0c9ad648..119b943fab40 100644 --- a/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl +++ b/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl @@ -34,16 +34,27 @@ description() -> %%-------------------------------------------------------------------- user_login_authentication(Username, AuthProps) -> - case http_req(p(user_path), q([{username, Username}] ++ extract_other_credentials(AuthProps))) of - {error, _} = E -> E; - "deny" -> {refused, "Denied by the backing HTTP service", []}; - "allow" ++ Rest -> Tags = [rabbit_data_coercion:to_atom(T) || - T <- string:tokens(Rest, " ")], - - {ok, #auth_user{username = Username, - tags = Tags, - impl = fun() -> proplists:delete(username, AuthProps) end}}; - Other -> {error, {bad_response, Other}} + Path = p(user_path), + Query = q([{username, Username}] ++ extract_other_credentials(AuthProps)), + case http_req(Path, Query) of + {error, _} = Err -> + Err; + "deny " ++ Reason -> + ?LOG_INFO("HTTP authentication denied for user '~ts': ~ts", + [Username, Reason]), + {refused, "Denied by the backing HTTP service", []}; + Body -> + case string:lowercase(Body) of + "deny" -> + {refused, "Denied by the backing HTTP service", []}; + "allow" ++ Rest -> + Tags = [rabbit_data_coercion:to_atom(T) + || T <- string:tokens(Rest, " ")], + {ok, #auth_user{ + username = Username, + tags = Tags, + impl = fun() -> proplists:delete(username, AuthProps) end}} + end end. %% When a protocol plugin uses an internal AMQP 0-9-1 client to interact with RabbitMQ core, @@ -153,13 +164,26 @@ context_as_parameters(_) -> []. bool_req(PathName, Props) -> - case http_req(p(PathName), q(Props)) of - "deny" -> false; - "allow" -> true; - E -> E + Path = p(PathName), + Query = q(Props), + case http_req(Path, Query) of + {error, _} = Err -> + Err; + "deny " ++ Reason -> + ?LOG_INFO("HTTP authorisation denied for path ~ts with query ~ts: ~ts", + [Path, Query, Reason]), + false; + Body -> + case string:lowercase(Body) of + "deny" -> + false; + "allow" -> + true + end end. -http_req(Path, Query) -> http_req(Path, Query, ?RETRY_ON_KEEPALIVE_CLOSED). +http_req(Path, Query) -> + http_req(Path, Query, ?RETRY_ON_KEEPALIVE_CLOSED). http_req(Path, Query, Retry) -> case do_http_req(Path, Query) of @@ -204,8 +228,10 @@ do_http_req(Path0, Query) -> {ok, {{_HTTP, Code, _}, _Headers, Body}} -> ?LOG_DEBUG("auth_backend_http: response code is ~tp, body: ~tp", [Code, Body]), case lists:member(Code, ?SUCCESSFUL_RESPONSE_CODES) of - true -> parse_resp(Body); - false -> {error, {Code, Body}} + true -> + string:strip(Body); + false -> + {error, {Code, Body}} end; {error, _} = E -> E @@ -240,8 +266,6 @@ escape(K, Map) when is_map(Map) -> escape(K, V) -> rabbit_data_coercion:to_list(K) ++ "=" ++ rabbit_http_util:quote_plus(V). -parse_resp(Resp) -> string:to_lower(string:strip(Resp)). - join_tags([]) -> ""; join_tags(Tags) -> Strings = [rabbit_data_coercion:to_list(T) || T <- Tags], diff --git a/deps/rabbitmq_auth_backend_http/test/auth_http_mock.erl b/deps/rabbitmq_auth_backend_http/test/auth_http_mock.erl index 5a5e724e9117..5d5fb76e025e 100644 --- a/deps/rabbitmq_auth_backend_http/test/auth_http_mock.erl +++ b/deps/rabbitmq_auth_backend_http/test/auth_http_mock.erl @@ -28,5 +28,5 @@ authenticate(QsVals, Users) -> {_OtherPassword, _, _} -> <<"deny">>; undefined -> - <<"deny">> + <<"deny unknown_user">> end. From 9c075ad5fd8a8da8d234fc2b5b06c14b25c5490d Mon Sep 17 00:00:00 2001 From: David Ansari Date: Wed, 8 Oct 2025 16:55:12 +0200 Subject: [PATCH 2/2] Optionally return authz refusal reason to client ## What? If the new config setting `authorization_failure_disclosure` for an authz backend is set to `true`, (`false` by default), RabbitMQ will return the reason why access was denied to the client. For now, only the HTTP auth backend supports this new config setting. ## Why? This helps debugging and troubleshooting directly in the client. Some users might not have access to the RabbitMQ logs, for other users it's cumbersome to correlate authz denial in the client with logs on the broker. For example, some customers would like to pass the reason why authorization was denied from their custom HTTP auth backend via RabbitMQ back to the client. ## How? Authz backends can now return `{false, Reason}` as an alternative to just `false` if access is denied. For security reasons, the additional denial reason by the authz backend will be returned to the client only if the operator opted in by setting `authorization_failure_disclosure` to `true`. Note that `authorization_failure_disclosure` applies only to already authenticated clients when they try to access resources (e.g. vhosts, exchanges, queues, topics). For security reasons, no detailed denial reason is returned to the client if **authentication** fails. Also note that `authorization_failure_disclosure` is set separately per auth backend instead of being set globally for all auth backends. This more fine granular configurability helps for use cases where the broker should reveal the authz denial reason for only a specific auth backend. --- deps/rabbit/src/rabbit_access_control.erl | 4 + deps/rabbit/src/rabbit_authz_backend.erl | 9 +- .../src/rabbit_auth_backend_cache.erl | 33 ++- .../src/rabbit_auth_cache.erl | 4 +- deps/rabbitmq_auth_backend_http/Makefile | 5 +- deps/rabbitmq_auth_backend_http/README.md | 6 +- .../schema/rabbitmq_auth_backend_http.schema | 3 + .../src/rabbit_auth_backend_http.erl | 70 ++--- .../test/auth_SUITE.erl | 250 ++++++------------ .../test/auth_http_server.erl | 34 +++ .../test/auth_unit_SUITE.erl | 196 ++++++++++++++ .../rabbitmq_auth_backend_http.snippets | 6 +- .../src/rabbit_mqtt_processor.erl | 9 +- 13 files changed, 397 insertions(+), 232 deletions(-) create mode 100644 deps/rabbitmq_auth_backend_http/test/auth_http_server.erl create mode 100644 deps/rabbitmq_auth_backend_http/test/auth_unit_SUITE.erl diff --git a/deps/rabbit/src/rabbit_access_control.erl b/deps/rabbit/src/rabbit_access_control.erl index b0b2ecf0f899..b1d6761d5b3a 100644 --- a/deps/rabbit/src/rabbit_access_control.erl +++ b/deps/rabbit/src/rabbit_access_control.erl @@ -361,6 +361,10 @@ check_access(Fun, Module, ErrStr, ErrArgs, ErrName) -> ok; false -> rabbit_misc:protocol_error(ErrName, ErrStr, ErrArgs); + {false, Reason} -> + FullErrStr = ErrStr ++ " by backend ~ts: ~ts", + FullErrArgs = ErrArgs ++ [Module, Reason], + rabbit_misc:protocol_error(ErrName, FullErrStr, FullErrArgs); {error, E} -> FullErrStr = ErrStr ++ ", backend ~ts returned an error: ~tp", FullErrArgs = ErrArgs ++ [Module, E], diff --git a/deps/rabbit/src/rabbit_authz_backend.erl b/deps/rabbit/src/rabbit_authz_backend.erl index fdf9c6c561b0..9c25abd44e78 100644 --- a/deps/rabbit/src/rabbit_authz_backend.erl +++ b/deps/rabbit/src/rabbit_authz_backend.erl @@ -32,38 +32,41 @@ %% Possible responses: %% true %% false +%% {false, Reason} %% {error, Error} %% Something went wrong. Log and die. -callback check_vhost_access(AuthUser :: rabbit_types:auth_user(), VHost :: rabbit_types:vhost(), AuthzData :: rabbit_types:authz_data()) -> - boolean() | {'error', any()}. + boolean() | {false, Reason :: string()} | {'error', any()}. %% Given #auth_user, resource and permission, can a user access a resource? %% %% Possible responses: %% true %% false +%% {false, Reason} %% {error, Error} %% Something went wrong. Log and die. -callback check_resource_access(rabbit_types:auth_user(), rabbit_types:r(atom()), rabbit_types:permission_atom(), rabbit_types:authz_context()) -> - boolean() | {'error', any()}. + boolean() | {false, Reason :: string()} | {'error', any()}. %% Given #auth_user, topic as resource, permission, and context, can a user access the topic? %% %% Possible responses: %% true %% false +%% {false, Reason} %% {error, Error} %% Something went wrong. Log and die. -callback check_topic_access(rabbit_types:auth_user(), rabbit_types:r(atom()), rabbit_types:permission_atom(), rabbit_types:topic_access_context()) -> - boolean() | {'error', any()}. + boolean() | {false, Reason :: string()} | {'error', any()}. %% Updates backend state that has expired. %% diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl index a952ccb0470f..748cc37d199b 100644 --- a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl +++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl @@ -36,30 +36,21 @@ user_login_authorization(Username, AuthProps) -> end). check_vhost_access(#auth_user{} = AuthUser, VHostPath, AuthzData) -> - with_cache(authz, {check_vhost_access, [AuthUser, VHostPath, AuthzData]}, - fun(true) -> success; - (false) -> refusal; - ({error, _} = Err) -> Err; - (_) -> unknown - end). + with_cache(authz, + {check_vhost_access, [AuthUser, VHostPath, AuthzData]}, + fun convert_backend_result/1). check_resource_access(#auth_user{} = AuthUser, #resource{} = Resource, Permission, AuthzContext) -> - with_cache(authz, {check_resource_access, [AuthUser, Resource, Permission, AuthzContext]}, - fun(true) -> success; - (false) -> refusal; - ({error, _} = Err) -> Err; - (_) -> unknown - end). + with_cache(authz, + {check_resource_access, [AuthUser, Resource, Permission, AuthzContext]}, + fun convert_backend_result/1). check_topic_access(#auth_user{} = AuthUser, #resource{} = Resource, Permission, Context) -> - with_cache(authz, {check_topic_access, [AuthUser, Resource, Permission, Context]}, - fun(true) -> success; - (false) -> refusal; - ({error, _} = Err) -> Err; - (_) -> unknown - end). + with_cache(authz, + {check_topic_access, [AuthUser, Resource, Permission, Context]}, + fun convert_backend_result/1). expiry_timestamp(_) -> never. @@ -67,6 +58,12 @@ expiry_timestamp(_) -> never. %% Implementation %% +convert_backend_result(true) -> success; +convert_backend_result(false) -> refusal; +convert_backend_result({false, _}) -> refusal; +convert_backend_result({error, _} = Err) -> Err; +convert_backend_result(_) -> unknown. + clear_cache_cluster_wide() -> Nodes = rabbit_nodes:list_running(), ?LOG_WARNING("Clearing auth_backend_cache in all nodes : ~p", [Nodes]), diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl index a8171133e9fb..3de66c4e36f5 100644 --- a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl +++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl @@ -18,7 +18,7 @@ -callback clear() -> ok. expiration(TTL) -> - erlang:system_time(milli_seconds) + TTL. + erlang:system_time(millisecond) + TTL. expired(Exp) -> - erlang:system_time(milli_seconds) > Exp. + erlang:system_time(millisecond) > Exp. diff --git a/deps/rabbitmq_auth_backend_http/Makefile b/deps/rabbitmq_auth_backend_http/Makefile index 67709e9afb1d..ac2906e389f0 100644 --- a/deps/rabbitmq_auth_backend_http/Makefile +++ b/deps/rabbitmq_auth_backend_http/Makefile @@ -10,7 +10,8 @@ define PROJECT_ENV {user_path, "http://localhost:8000/auth/user"}, {vhost_path, "http://localhost:8000/auth/vhost"}, {resource_path, "http://localhost:8000/auth/resource"}, - {topic_path, "http://localhost:8000/auth/topic"} + {topic_path, "http://localhost:8000/auth/topic"}, + {authorization_failure_disclosure, false} ] endef @@ -20,7 +21,7 @@ endef LOCAL_DEPS = ssl inets crypto public_key DEPS = rabbit_common rabbit amqp_client -TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers cowboy +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers cowboy rabbitmq_amqp_client DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk diff --git a/deps/rabbitmq_auth_backend_http/README.md b/deps/rabbitmq_auth_backend_http/README.md index 6f34ab9290b4..1c998db508fc 100644 --- a/deps/rabbitmq_auth_backend_http/README.md +++ b/deps/rabbitmq_auth_backend_http/README.md @@ -53,6 +53,7 @@ auth_http.user_path = http://some-server/auth/user auth_http.vhost_path = http://some-server/auth/vhost auth_http.resource_path = http://some-server/auth/resource auth_http.topic_path = http://some-server/auth/topic +auth_http.authorization_failure_disclosure = false ``` In the [`advanced.config` format](https://www.rabbitmq.com/configure.html#advanced-config-file): @@ -65,7 +66,8 @@ In the [`advanced.config` format](https://www.rabbitmq.com/configure.html#advanc {user_path, "http(s)://some-server/auth/user"}, {vhost_path, "http(s)://some-server/auth/vhost"}, {resource_path, "http(s)://some-server/auth/resource"}, - {topic_path, "http(s)://some-server/auth/topic"}]} + {topic_path, "http(s)://some-server/auth/topic"}, + {authorization_failure_disclosure, false}]} ]. ``` @@ -124,6 +126,8 @@ containing: * `deny`: deny access to the user / vhost / resource * `deny `: deny access to the user / vhost / resource. RabbitMQ will log the `` at INFO level. + If `auth_http.authorization_failure_disclosure` is set to `true` (the default is `false` for security reasons) + RabbitMQ will additionally forward the `` to AMQP clients. * `allow`: allow access to the user / vhost / resource * `allow [list of tags]` (for `user_path` only): allow access, and mark the user as an having the tags listed diff --git a/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema b/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema index fccf97383ce8..426575d18921 100644 --- a/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema +++ b/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema @@ -23,6 +23,9 @@ {mapping, "auth_http.connection_timeout", "rabbitmq_auth_backend_http.connection_timeout", [{datatype, integer}]}. +{mapping, "auth_http.authorization_failure_disclosure", "rabbitmq_auth_backend_http.authorization_failure_disclosure", [ + {datatype, {enum, [true, false]}}]}. + %% TLS options {mapping, "auth_http.ssl_options", "rabbitmq_auth_backend_http.ssl_options", [ diff --git a/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl b/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl index 119b943fab40..9cb956ff0065 100644 --- a/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl +++ b/deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl @@ -25,6 +25,8 @@ -define(SUCCESSFUL_RESPONSE_CODES, [200, 201]). +-define(APP, rabbitmq_auth_backend_http). + %%-------------------------------------------------------------------- description() -> @@ -37,7 +39,7 @@ user_login_authentication(Username, AuthProps) -> Path = p(user_path), Query = q([{username, Username}] ++ extract_other_credentials(AuthProps)), case http_req(Path, Query) of - {error, _} = Err -> + {error, _} = Err -> Err; "deny " ++ Reason -> ?LOG_INFO("HTTP authentication denied for user '~ts': ~ts", @@ -53,7 +55,9 @@ user_login_authentication(Username, AuthProps) -> {ok, #auth_user{ username = Username, tags = Tags, - impl = fun() -> proplists:delete(username, AuthProps) end}} + impl = fun() -> proplists:delete(username, AuthProps) end}}; + Other -> + {error, {bad_response, Other}} end end. @@ -120,34 +124,34 @@ check_vhost_access(#auth_user{username = Username, tags = Tags}, VHost, do_check_vhost_access(Username, Tags, VHost, Ip, AuthzData) -> OptionsParameters = context_as_parameters(AuthzData), - bool_req(vhost_path, [{username, Username}, - {vhost, VHost}, - {ip, Ip}, - {tags, join_tags(Tags)}] ++ OptionsParameters). + req(vhost_path, [{username, Username}, + {vhost, VHost}, + {ip, Ip}, + {tags, join_tags(Tags)}] ++ OptionsParameters). check_resource_access(#auth_user{username = Username, tags = Tags}, #resource{virtual_host = VHost, kind = Type, name = Name}, Permission, AuthzContext) -> OptionsParameters = context_as_parameters(AuthzContext), - bool_req(resource_path, [{username, Username}, - {vhost, VHost}, - {resource, Type}, - {name, Name}, - {permission, Permission}, - {tags, join_tags(Tags)}] ++ OptionsParameters). + req(resource_path, [{username, Username}, + {vhost, VHost}, + {resource, Type}, + {name, Name}, + {permission, Permission}, + {tags, join_tags(Tags)}] ++ OptionsParameters). check_topic_access(#auth_user{username = Username, tags = Tags}, #resource{virtual_host = VHost, kind = topic = Type, name = Name}, Permission, Context) -> OptionsParameters = context_as_parameters(Context), - bool_req(topic_path, [{username, Username}, - {vhost, VHost}, - {resource, Type}, - {name, Name}, - {permission, Permission}, - {tags, join_tags(Tags)}] ++ OptionsParameters). + req(topic_path, [{username, Username}, + {vhost, VHost}, + {resource, Type}, + {name, Name}, + {permission, Permission}, + {tags, join_tags(Tags)}] ++ OptionsParameters). expiry_timestamp(_) -> never. @@ -163,16 +167,21 @@ context_as_parameters(Options) when is_map(Options) -> context_as_parameters(_) -> []. -bool_req(PathName, Props) -> +req(PathName, Props) -> Path = p(PathName), Query = q(Props), case http_req(Path, Query) of {error, _} = Err -> Err; "deny " ++ Reason -> - ?LOG_INFO("HTTP authorisation denied for path ~ts with query ~ts: ~ts", + ?LOG_INFO("HTTP authorization denied for path ~ts with query '~ts': ~ts", [Path, Query, Reason]), - false; + case application:get_env(?APP, authorization_failure_disclosure) of + {ok, true} -> + {false, Reason}; + _ -> + false + end; Body -> case string:lowercase(Body) of "deny" -> @@ -201,7 +210,7 @@ do_http_req(Path0, Query) -> {host, Host} = lists:keyfind(host, 1, URI), {port, Port} = lists:keyfind(port, 1, URI), HostHdr = rabbit_misc:format("~ts:~b", [Host, Port]), - {ok, Method} = application:get_env(rabbitmq_auth_backend_http, http_method), + {ok, Method} = application:get_env(?APP, http_method), Request = case rabbit_data_coercion:to_atom(Method) of get -> Path = Path0 ++ "?" ++ Query, @@ -212,21 +221,22 @@ do_http_req(Path0, Query) -> {Path0, [{"Host", HostHdr}], "application/x-www-form-urlencoded", Query} end, RequestTimeout = - case application:get_env(rabbitmq_auth_backend_http, request_timeout) of + case application:get_env(?APP, request_timeout) of {ok, Val1} -> Val1; _ -> infinity end, ConnectionTimeout = - case application:get_env(rabbitmq_auth_backend_http, connection_timeout) of + case application:get_env(?APP, connection_timeout) of {ok, Val2} -> Val2; _ -> RequestTimeout end, ?LOG_DEBUG("auth_backend_http: request timeout: ~tp, connection timeout: ~tp", [RequestTimeout, ConnectionTimeout]), HttpOpts = [{timeout, RequestTimeout}, {connect_timeout, ConnectionTimeout}] ++ ssl_options(), - case httpc:request(Method, Request, HttpOpts, []) of - {ok, {{_HTTP, Code, _}, _Headers, Body}} -> - ?LOG_DEBUG("auth_backend_http: response code is ~tp, body: ~tp", [Code, Body]), + case httpc:request(Method, Request, HttpOpts, [{body_format, binary}]) of + {ok, {{_HTTP, Code, _}, _Headers, BodyBin}} -> + Body = unicode:characters_to_list(BodyBin), + ?LOG_DEBUG("auth_backend_http: response code is ~tp, body: '~ts'", [Code, Body]), case lists:member(Code, ?SUCCESSFUL_RESPONSE_CODES) of true -> string:strip(Body); @@ -238,10 +248,10 @@ do_http_req(Path0, Query) -> end. ssl_options() -> - case application:get_env(rabbitmq_auth_backend_http, ssl_options) of + case application:get_env(?APP, ssl_options) of {ok, Opts0} when is_list(Opts0) -> Opts1 = [{ssl, rabbit_ssl_options:fix_client(Opts0)}], - case application:get_env(rabbitmq_auth_backend_http, ssl_hostname_verification) of + case application:get_env(?APP, ssl_hostname_verification) of {ok, wildcard} -> ?LOG_DEBUG("Enabling wildcard-aware hostname verification for HTTP client connections"), %% Needed for HTTPS connections that connect to servers that use wildcard certificates. @@ -254,7 +264,7 @@ ssl_options() -> end. p(PathName) -> - {ok, Path} = application:get_env(rabbitmq_auth_backend_http, PathName), + {ok, Path} = application:get_env(?APP, PathName), Path. q(Args) -> diff --git a/deps/rabbitmq_auth_backend_http/test/auth_SUITE.erl b/deps/rabbitmq_auth_backend_http/test/auth_SUITE.erl index e7bddd59f04a..fcd005ae60de 100644 --- a/deps/rabbitmq_auth_backend_http/test/auth_SUITE.erl +++ b/deps/rabbitmq_auth_backend_http/test/auth_SUITE.erl @@ -2,195 +2,105 @@ %% License, v. 2.0. If a copy of the MPL was not distributed with this %% file, You can obtain one at https://mozilla.org/MPL/2.0/. %% -%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% -module(auth_SUITE). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). --include_lib("rabbit_common/include/rabbit.hrl"). - --compile(export_all). - --define(AUTH_PORT, 8000). --define(USER_PATH, "/auth/user"). --define(ALLOWED_USER, #{username => <<"Ala1">>, - password => <<"Kocur">>, - expected_credentials => [username, password], - tags => [policymaker, monitoring]}). --define(ALLOWED_USER_2, #{username => <<"Ala3">>, - expected_credentials => [username], - tags => [policymaker, monitoring]}). --define(ALLOWED_USER_WITH_EXTRA_CREDENTIALS, #{username => <<"Ala2">>, - password => <<"Kocur">>, - client_id => <<"some_id">>, - expected_credentials => [username, password, client_id], - tags => [policymaker, monitoring]}). --define(DENIED_USER, #{username => <<"Alice">>, - password => <<"Cat">> - }). +-include_lib("amqp10_common/include/amqp10_framing.hrl"). + +-compile([nowarn_export_all, + export_all]). all() -> [ - {group, over_https}, - {group, over_http} + {group, cluster_size_1} ]. groups() -> [ - {over_http, [], shared()}, - {over_https, [], shared()} - ]. - -shared() -> - [ - grants_access_to_user, - denies_access_to_user, - grants_access_to_user_passing_additional_required_authprops, - grants_access_to_user_skipping_internal_authprops, - grants_access_to_user_with_credentials_in_rabbit_auth_backend_http, - grants_access_to_user_with_credentials_in_rabbit_auth_backend_cache, - grants_access_to_ssl_user_without_a_password - ]. + {cluster_size_1, [shuffle], + [ + authorization_failure_disclosure + ] + }]. init_per_suite(Config) -> - rabbit_ct_helpers:run_setup_steps(Config) ++ - [{allowed_user, ?ALLOWED_USER}, - {allowed_user_2, ?ALLOWED_USER_2}, - {allowed_user_with_extra_credentials, ?ALLOWED_USER_WITH_EXTRA_CREDENTIALS}, - {denied_user, ?DENIED_USER}]. - -init_per_group(over_http, Config) -> - configure_http_auth_backend("http", Config), - {User1, Tuple1} = extractUserTuple(?ALLOWED_USER), - {User2, Tuple2} = extractUserTuple(?ALLOWED_USER_WITH_EXTRA_CREDENTIALS), - start_http_auth_server(?AUTH_PORT, ?USER_PATH, #{User1 => Tuple1, User2 => Tuple2}), - Config; - -init_per_group(over_https, Config) -> - configure_http_auth_backend("https", Config), - {User1, Tuple1} = extractUserTuple(?ALLOWED_USER), - {User2, Tuple2} = extractUserTuple(?ALLOWED_USER_2), - {User3, Tuple3} = extractUserTuple(?ALLOWED_USER_WITH_EXTRA_CREDENTIALS), - CertsDir = ?config(rmq_certsdir, Config), - start_https_auth_server(?AUTH_PORT, CertsDir, ?USER_PATH, #{ - User1 => Tuple1, - User3 => Tuple3, - User2 => Tuple2}), - Config ++ [{group, over_https}]. - -extractUserTuple(User) -> - #{username := Username, tags := Tags, expected_credentials := ExpectedCredentials} = User, - Password = case maps:get(password, User, undefined) of - undefined -> none; - P -> P - end, - {Username, {Password, Tags, ExpectedCredentials}}. + {ok, _} = application:ensure_all_started(rabbitmq_amqp_client), + start_http_auth_server(), + rabbit_ct_helpers:log_environment(), + Config. end_per_suite(Config) -> + ok = stop_http_auth_server(), Config. -end_per_group(over_http, Config) -> - undo_configure_http_auth_backend("http", Config), - stop_http_auth_server(); -end_per_group(over_https, Config) -> - undo_configure_http_auth_backend("https", Config), - stop_http_auth_server(). - -grants_access_to_user(Config) -> - #{username := U, password := P, tags := T} = ?config(allowed_user, Config), - AuthProps = [{password, P}], - {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), - ?assertMatch({U, T, AuthProps}, - {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). - -grants_access_to_ssl_user_without_a_password(Config) -> - case ?config(group, Config) of - over_https -> - #{username := U, tags := T} = ?config(allowed_user_2, Config), - {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, []), - ?assertMatch({U, T, []}, - {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}); - _ ->{skip, "Requires https"} - end. - -denies_access_to_user(Config) -> - #{username := U, password := P} = ?config(denied_user, Config), - ?assertMatch({refused, "Denied by the backing HTTP service", []}, - rabbit_auth_backend_http:user_login_authentication(U, [{password, P}])). - -grants_access_to_user_passing_additional_required_authprops(Config) -> - #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), - AuthProps = [{password, P}, {client_id, ClientId}], - {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), - ?assertMatch({U, T, AuthProps}, - {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). - -grants_access_to_user_skipping_internal_authprops(Config) -> - #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), - AuthProps = [{password, P}, {client_id, ClientId}, {rabbit_any_internal_property, <<"some value">>}], - {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), - ?assertMatch({U, T, AuthProps}, - {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). - -grants_access_to_user_with_credentials_in_rabbit_auth_backend_http(Config) -> - #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), - AuthProps = [{rabbit_auth_backend_http, fun() -> [{password, P}, {client_id, ClientId}] end}], - {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), - ?assertMatch({U, T, AuthProps}, - {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). - -grants_access_to_user_with_credentials_in_rabbit_auth_backend_cache(Config) -> - #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), - AuthProps = [{rabbit_auth_backend_cache, fun() -> [{password, P}, {client_id, ClientId}] end}], - {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), - ?assertMatch({U, T, AuthProps}, - {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). - -%%% HELPERS - -configure_http_auth_backend(Scheme, Config) -> - [application:set_env(rabbitmq_auth_backend_http, K, V) || {K, V} <- generate_backend_config(Scheme, Config)]. -undo_configure_http_auth_backend(Scheme, Config) -> - [application:unset_env(rabbitmq_auth_backend_http, K) || {K, _V} <- generate_backend_config(Scheme, Config)]. - -start_http_auth_server(Port, Path, Users) -> - {ok, _} = application:ensure_all_started(inets), - {ok, _} = application:ensure_all_started(cowboy), - Dispatch = cowboy_router:compile([{'_', [{Path, auth_http_mock, Users}]}]), - {ok, _} = cowboy:start_clear( - mock_http_auth_listener, [{port, Port}], #{env => #{dispatch => Dispatch}}). - -start_https_auth_server(Port, CertsDir, Path, Users) -> +init_per_group(_Group, Config) -> + Suffix = rabbit_ct_helpers:testcase_absname(Config, "", "-"), + Config1 = rabbit_ct_helpers:set_config( + Config, [{rmq_nodename_suffix, Suffix}]), + Config2 = rabbit_ct_helpers:merge_app_env( + Config1, + [ + {rabbit, [{auth_backends, [rabbit_auth_backend_http]}]}, + {rabbitmq_auth_backend_http, [{authorization_failure_disclosure, true}]} + ]), + rabbit_ct_helpers:run_setup_steps( + Config2, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()). + +end_per_group(_Group, Config) -> + rabbit_ct_helpers:run_teardown_steps( + Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()). + +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase). + +end_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_finished(Config, Testcase). + +start_http_auth_server() -> {ok, _} = application:ensure_all_started(inets), - {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(cowboy), - Dispatch = cowboy_router:compile([{'_', [{Path, auth_http_mock, Users}]}]), - {ok, _} = cowboy:start_tls(mock_http_auth_listener, - [{port, Port}, - {certfile, filename:join([CertsDir, "server", "cert.pem"])}, - {keyfile, filename:join([CertsDir, "server", "key.pem"])}], - #{env => #{dispatch => Dispatch}}). + Dispatch = cowboy_router:compile([{'_', [{'_', auth_http_server, #{}}]}]), + {ok, _} = cowboy:start_clear(auth_http_listener, + [{port, 8000}], + #{env => #{dispatch => Dispatch}}). stop_http_auth_server() -> - cowboy:stop_listener(mock_http_auth_listener). - -generate_backend_config(Scheme, Config) -> - Config0 = [{http_method, get}, - {user_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ ?USER_PATH}, - {vhost_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/vhost"}, - {resource_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/resource"}, - {topic_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/topic"}], - Config1 = case Scheme of - "https" -> - CertsDir = ?config(rmq_certsdir, Config), - [{ssl_options, [ - {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, - {certfile, filename:join([CertsDir, "server", "cert.pem"])}, - {keyfile, filename:join([CertsDir, "server", "key.pem"])}, - {verify, verify_peer}, - {fail_if_no_peer_cert, false}] - }]; - "http" -> [] - end, - Config0 ++ Config1. + cowboy:stop_listener(auth_http_listener). + +authorization_failure_disclosure(Config) -> + OpnConf = amqp_connection_config(Config), + {ok, Connection} = amqp10_client:open_connection(OpnConf), + {ok, Session} = amqp10_client:begin_session_sync(Connection), + {ok, LinkPair} = rabbitmq_amqp_client:attach_management_link_pair_sync(Session, <<"pair">>), + + QName = <<"my-queue">>, + {ok, _} = rabbitmq_amqp_client:declare_queue(LinkPair, QName, #{}), + {ok, _} = rabbitmq_amqp_client:delete_queue(LinkPair, QName), + + XName = <<"my-exchange">>, + {error, {session_ended, Error}} = rabbitmq_amqp_client:declare_exchange(LinkPair, XName, #{}), + %% We expect to receive the full denial reason as sent by the HTTP auth server. + ExpectedReason = <<"configure access to exchange 'my-exchange' in vhost '/' refused for user " + "'guest' by backend rabbit_auth_backend_http: Creating or deleting " + "exchanges is forbidden for all client apps ❌"/utf8>>, + ?assertEqual(#'v1_0.error'{condition = ?V_1_0_AMQP_ERROR_UNAUTHORIZED_ACCESS, + description = {utf8, ExpectedReason}}, + Error), + + ok = amqp10_client:close_connection(Connection). + +amqp_connection_config(Config) -> + Host = proplists:get_value(rmq_hostname, Config), + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp), + #{address => Host, + port => Port, + container_id => <<"my container">>, + sasl => {plain, <<"guest">>, <<"guest">>}}. diff --git a/deps/rabbitmq_auth_backend_http/test/auth_http_server.erl b/deps/rabbitmq_auth_backend_http/test/auth_http_server.erl new file mode 100644 index 000000000000..c8e6d9876bab --- /dev/null +++ b/deps/rabbitmq_auth_backend_http/test/auth_http_server.erl @@ -0,0 +1,34 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. +%% + +-module(auth_http_server). + +-export([init/2]). + +init(Req0, #{} = State) -> + Path = cowboy_req:path(Req0), + Query = cowboy_req:parse_qs(Req0), + ct:pal("~s received request on path ~s with query ~tp", + [?MODULE, Path, Query]), + RespBody = handle(Path, Query), + Req = cowboy_req:reply(200, + #{<<"content-type">> => <<"text/plain; charset=utf-8">>}, + RespBody, + Req0), + {ok, Req, State}. + +handle(<<"/auth/user">>, _Query) -> + <<"allow">>; +handle(<<"/auth/vhost">>, _Query) -> + <<"allow">>; +handle(<<"/auth/resource">>, Query) -> + case proplists:get_value(<<"resource">>, Query) of + <<"queue">> -> + <<"allow">>; + <<"exchange">> -> + <<"deny Creating or deleting exchanges is forbidden for all client apps ❌"/utf8>> + end. diff --git a/deps/rabbitmq_auth_backend_http/test/auth_unit_SUITE.erl b/deps/rabbitmq_auth_backend_http/test/auth_unit_SUITE.erl new file mode 100644 index 000000000000..82050b3aae22 --- /dev/null +++ b/deps/rabbitmq_auth_backend_http/test/auth_unit_SUITE.erl @@ -0,0 +1,196 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. + +-module(auth_unit_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("rabbit_common/include/rabbit.hrl"). + +-compile(export_all). + +-define(AUTH_PORT, 8001). +-define(USER_PATH, "/auth/user"). +-define(ALLOWED_USER, #{username => <<"Ala1">>, + password => <<"Kocur">>, + expected_credentials => [username, password], + tags => [policymaker, monitoring]}). +-define(ALLOWED_USER_2, #{username => <<"Ala3">>, + expected_credentials => [username], + tags => [policymaker, monitoring]}). +-define(ALLOWED_USER_WITH_EXTRA_CREDENTIALS, #{username => <<"Ala2">>, + password => <<"Kocur">>, + client_id => <<"some_id">>, + expected_credentials => [username, password, client_id], + tags => [policymaker, monitoring]}). +-define(DENIED_USER, #{username => <<"Alice">>, + password => <<"Cat">> + }). + +all() -> + [ + {group, over_https}, + {group, over_http} + ]. + +groups() -> + [ + {over_http, [], shared()}, + {over_https, [], shared()} + ]. + +shared() -> + [ + grants_access_to_user, + denies_access_to_user, + grants_access_to_user_passing_additional_required_authprops, + grants_access_to_user_skipping_internal_authprops, + grants_access_to_user_with_credentials_in_rabbit_auth_backend_http, + grants_access_to_user_with_credentials_in_rabbit_auth_backend_cache, + grants_access_to_ssl_user_without_a_password + ]. + +init_per_suite(Config) -> + rabbit_ct_helpers:run_setup_steps(Config) ++ + [{allowed_user, ?ALLOWED_USER}, + {allowed_user_2, ?ALLOWED_USER_2}, + {allowed_user_with_extra_credentials, ?ALLOWED_USER_WITH_EXTRA_CREDENTIALS}, + {denied_user, ?DENIED_USER}]. + +init_per_group(over_http, Config) -> + configure_http_auth_backend("http", Config), + {User1, Tuple1} = extractUserTuple(?ALLOWED_USER), + {User2, Tuple2} = extractUserTuple(?ALLOWED_USER_WITH_EXTRA_CREDENTIALS), + start_http_auth_server(?AUTH_PORT, ?USER_PATH, #{User1 => Tuple1, User2 => Tuple2}), + Config; + +init_per_group(over_https, Config) -> + configure_http_auth_backend("https", Config), + {User1, Tuple1} = extractUserTuple(?ALLOWED_USER), + {User2, Tuple2} = extractUserTuple(?ALLOWED_USER_2), + {User3, Tuple3} = extractUserTuple(?ALLOWED_USER_WITH_EXTRA_CREDENTIALS), + CertsDir = ?config(rmq_certsdir, Config), + start_https_auth_server(?AUTH_PORT, CertsDir, ?USER_PATH, #{ + User1 => Tuple1, + User3 => Tuple3, + User2 => Tuple2}), + Config ++ [{group, over_https}]. + +extractUserTuple(User) -> + #{username := Username, tags := Tags, expected_credentials := ExpectedCredentials} = User, + Password = case maps:get(password, User, undefined) of + undefined -> none; + P -> P + end, + {Username, {Password, Tags, ExpectedCredentials}}. + +end_per_suite(Config) -> + Config. + +end_per_group(over_http, Config) -> + undo_configure_http_auth_backend("http", Config), + stop_http_auth_server(); +end_per_group(over_https, Config) -> + undo_configure_http_auth_backend("https", Config), + stop_http_auth_server(). + +grants_access_to_user(Config) -> + #{username := U, password := P, tags := T} = ?config(allowed_user, Config), + AuthProps = [{password, P}], + {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), + ?assertMatch({U, T, AuthProps}, + {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). + +grants_access_to_ssl_user_without_a_password(Config) -> + case ?config(group, Config) of + over_https -> + #{username := U, tags := T} = ?config(allowed_user_2, Config), + {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, []), + ?assertMatch({U, T, []}, + {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}); + _ ->{skip, "Requires https"} + end. + +denies_access_to_user(Config) -> + #{username := U, password := P} = ?config(denied_user, Config), + ?assertMatch({refused, "Denied by the backing HTTP service", []}, + rabbit_auth_backend_http:user_login_authentication(U, [{password, P}])). + +grants_access_to_user_passing_additional_required_authprops(Config) -> + #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), + AuthProps = [{password, P}, {client_id, ClientId}], + {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), + ?assertMatch({U, T, AuthProps}, + {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). + +grants_access_to_user_skipping_internal_authprops(Config) -> + #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), + AuthProps = [{password, P}, {client_id, ClientId}, {rabbit_any_internal_property, <<"some value">>}], + {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), + ?assertMatch({U, T, AuthProps}, + {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). + +grants_access_to_user_with_credentials_in_rabbit_auth_backend_http(Config) -> + #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), + AuthProps = [{rabbit_auth_backend_http, fun() -> [{password, P}, {client_id, ClientId}] end}], + {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), + ?assertMatch({U, T, AuthProps}, + {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). + +grants_access_to_user_with_credentials_in_rabbit_auth_backend_cache(Config) -> + #{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config), + AuthProps = [{rabbit_auth_backend_cache, fun() -> [{password, P}, {client_id, ClientId}] end}], + {ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps), + ?assertMatch({U, T, AuthProps}, + {User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}). + +%%% HELPERS + +configure_http_auth_backend(Scheme, Config) -> + [application:set_env(rabbitmq_auth_backend_http, K, V) || {K, V} <- generate_backend_config(Scheme, Config)]. +undo_configure_http_auth_backend(Scheme, Config) -> + [application:unset_env(rabbitmq_auth_backend_http, K) || {K, _V} <- generate_backend_config(Scheme, Config)]. + +start_http_auth_server(Port, Path, Users) -> + {ok, _} = application:ensure_all_started(inets), + {ok, _} = application:ensure_all_started(cowboy), + Dispatch = cowboy_router:compile([{'_', [{Path, auth_http_mock, Users}]}]), + {ok, _} = cowboy:start_clear( + mock_http_auth_listener, [{port, Port}], #{env => #{dispatch => Dispatch}}). + +start_https_auth_server(Port, CertsDir, Path, Users) -> + {ok, _} = application:ensure_all_started(inets), + {ok, _} = application:ensure_all_started(ssl), + {ok, _} = application:ensure_all_started(cowboy), + Dispatch = cowboy_router:compile([{'_', [{Path, auth_http_mock, Users}]}]), + {ok, _} = cowboy:start_tls(mock_http_auth_listener, + [{port, Port}, + {certfile, filename:join([CertsDir, "server", "cert.pem"])}, + {keyfile, filename:join([CertsDir, "server", "key.pem"])}], + #{env => #{dispatch => Dispatch}}). + +stop_http_auth_server() -> + cowboy:stop_listener(mock_http_auth_listener). + +generate_backend_config(Scheme, Config) -> + Config0 = [{http_method, get}, + {user_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ ?USER_PATH}, + {vhost_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/vhost"}, + {resource_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/resource"}, + {topic_path, Scheme ++ "://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/topic"}], + Config1 = case Scheme of + "https" -> + CertsDir = ?config(rmq_certsdir, Config), + [{ssl_options, [ + {cacertfile, filename:join([CertsDir, "testca", "cacert.pem"])}, + {certfile, filename:join([CertsDir, "server", "cert.pem"])}, + {keyfile, filename:join([CertsDir, "server", "key.pem"])}, + {verify, verify_peer}, + {fail_if_no_peer_cert, false}] + }]; + "http" -> [] + end, + Config0 ++ Config1. diff --git a/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets b/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets index 7d94d78bbc16..7d630e6dfca4 100644 --- a/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets +++ b/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets @@ -3,13 +3,15 @@ auth_http.http_method = post auth_http.user_path = http://some-server/auth/user auth_http.vhost_path = http://some-server/auth/vhost - auth_http.resource_path = http://some-server/auth/resource", + auth_http.resource_path = http://some-server/auth/resource + auth_http.authorization_failure_disclosure = true", [{rabbit,[{auth_backends,[rabbit_auth_backend_http]}]}, {rabbitmq_auth_backend_http, [{http_method, post}, {user_path,"http://some-server/auth/user"}, {vhost_path,"http://some-server/auth/vhost"}, - {resource_path,"http://some-server/auth/resource"}]}], + {resource_path,"http://some-server/auth/resource"}, + {authorization_failure_disclosure, true}]}], [rabbitmq_auth_backend_http]}, {default_http_method, "auth_backends.1 = http diff --git a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl index 48ab2a86977d..e20c0b3267c3 100644 --- a/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl +++ b/deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl @@ -1609,8 +1609,9 @@ binding_action_with_checks(QName, TopicFilter, BindingArgs, Action, fun rabbit_binding:Action/2, AuthState) else {error, Reason} = Err -> - ?LOG_ERROR("Failed to ~s binding between ~s and ~s for topic filter ~s: ~p", - [Action, rabbit_misc:rs(ExchangeName), rabbit_misc:rs(QName), TopicFilter, Reason]), + ?LOG_ERROR( + "Failed to ~s binding between ~ts and ~ts for topic filter ~ts: ~tp", + [Action, rabbit_misc:rs(ExchangeName), rabbit_misc:rs(QName), TopicFilter, Reason]), Err end. @@ -2292,7 +2293,7 @@ check_resource_access(User, Resource, Perm, Context) -> catch exit:#amqp_error{name = access_refused, explanation = Msg} -> - ?LOG_ERROR("MQTT resource access refused: ~s", [Msg]), + ?LOG_ERROR("MQTT resource access refused: ~ts", [Msg]), {error, access_refused} end end. @@ -2326,7 +2327,7 @@ check_topic_access( catch exit:#amqp_error{name = access_refused, explanation = Msg} -> - ?LOG_ERROR("MQTT topic access refused: ~s", [Msg]), + ?LOG_ERROR("MQTT topic access refused: ~ts", [Msg]), {error, access_refused} end end.