From 8ffbddd554f924f0cba2a0789cf63f1925035015 Mon Sep 17 00:00:00 2001 From: Luke Bakken Date: Thu, 21 Aug 2025 16:32:58 -0700 Subject: [PATCH] Implement LDAP credentials validation via HTTP API See discussion #14244 Follow-up to #14414 Cherry-picked from 9a4cb9c881eeb1327570c4816685bcb93ff13b06 and then modified to move validation API endpoint to a separate plugin, `rabbitmq_auth_backend_ldap_management`. These changes will allow a user to make an HTTP API request to... ``` /api/ldap/validate/simple-bind ``` ...with an appropriate JSON body, and the plugin will attempt a connection to the specified LDAP server using the provided credentials. This allows validation that a connection can be made to an LDAP server from a RabbitMQ cluster environment. --- .github/workflows/test-make-tests.yaml | 1 + .github/workflows/test-make-type-check.yaml | 1 + .gitignore | 1 + Makefile | 1 + .../test/system_SUITE.erl | 161 +----- .../test/system_SUITE_data/init-slapd.sh | 128 +---- .../test/system_SUITE_data/ldif | 1 + .../Makefile | 16 + .../README.md | 54 ++ .../src/rabbit_auth_backend_ldap_mgmt.erl | 280 ++++++++++ .../test/system_SUITE.erl | 501 ++++++++++++++++++ .../test/system_SUITE_data/init-slapd.sh | 1 + .../test/system_SUITE_data/ldif | 1 + deps/rabbitmq_ct_helpers/Makefile | 3 +- .../include/rabbit_ldap_test.hrl | 36 ++ .../include/rabbit_mgmt_test.hrl | 1 + .../src/rabbit_ct_ldap_seed.erl} | 2 +- .../src/rabbit_ct_ldap_utils.erl | 149 ++++++ deps/rabbitmq_ct_helpers/tools/init-slapd.sh | 136 +++++ .../tools/ldif}/README.md | 0 .../tools/ldif}/global.ldif | 0 .../tools/ldif}/memberof_init.ldif | 0 .../tools/ldif}/refint_1.ldif | 0 .../tools/ldif}/refint_2.ldif | 0 .../src/rabbit_mgmt_util.erl | 7 +- mk/github-actions.mk | 1 + plugins.mk | 1 + rabbitmq-components.mk | 1 + 28 files changed, 1208 insertions(+), 276 deletions(-) mode change 100755 => 120000 deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh create mode 120000 deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/ldif create mode 100644 deps/rabbitmq_auth_backend_ldap_management/Makefile create mode 100644 deps/rabbitmq_auth_backend_ldap_management/README.md create mode 100644 deps/rabbitmq_auth_backend_ldap_management/src/rabbit_auth_backend_ldap_mgmt.erl create mode 100644 deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE.erl create mode 120000 deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/init-slapd.sh create mode 120000 deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/ldif create mode 100644 deps/rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl rename deps/{rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl => rabbitmq_ct_helpers/src/rabbit_ct_ldap_seed.erl} (99%) create mode 100644 deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_utils.erl create mode 100755 deps/rabbitmq_ct_helpers/tools/init-slapd.sh rename deps/{rabbitmq_auth_backend_ldap/example => rabbitmq_ct_helpers/tools/ldif}/README.md (100%) rename deps/{rabbitmq_auth_backend_ldap/example => rabbitmq_ct_helpers/tools/ldif}/global.ldif (100%) rename deps/{rabbitmq_auth_backend_ldap/example => rabbitmq_ct_helpers/tools/ldif}/memberof_init.ldif (100%) rename deps/{rabbitmq_auth_backend_ldap/example => rabbitmq_ct_helpers/tools/ldif}/refint_1.ldif (100%) rename deps/{rabbitmq_auth_backend_ldap/example => rabbitmq_ct_helpers/tools/ldif}/refint_2.ldif (100%) diff --git a/.github/workflows/test-make-tests.yaml b/.github/workflows/test-make-tests.yaml index 8fe2f2bfa271..6d87595e3e69 100644 --- a/.github/workflows/test-make-tests.yaml +++ b/.github/workflows/test-make-tests.yaml @@ -100,6 +100,7 @@ jobs: - rabbitmq_auth_backend_cache - rabbitmq_auth_backend_http - rabbitmq_auth_backend_ldap + - rabbitmq_auth_backend_ldap_management - rabbitmq_auth_backend_oauth2 - rabbitmq_auth_mechanism_ssl - rabbitmq_aws diff --git a/.github/workflows/test-make-type-check.yaml b/.github/workflows/test-make-type-check.yaml index 57780e4f9788..039df528ef98 100644 --- a/.github/workflows/test-make-type-check.yaml +++ b/.github/workflows/test-make-type-check.yaml @@ -29,6 +29,7 @@ jobs: - rabbitmq_auth_backend_cache - rabbitmq_auth_backend_http - rabbitmq_auth_backend_ldap + - rabbitmq_auth_backend_ldap_management - rabbitmq_auth_backend_oauth2 - rabbitmq_auth_mechanism_ssl - rabbitmq_aws diff --git a/.gitignore b/.gitignore index 272050aff697..eef66ca80a63 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ elvis !/deps/rabbitmq_auth_backend_http/ !/deps/rabbitmq_auth_backend_internal_loopback/ !/deps/rabbitmq_auth_backend_ldap/ +!/deps/rabbitmq_auth_backend_ldap_management/ !/deps/rabbitmq_auth_backend_oauth2/ !/deps/rabbitmq_auth_mechanism_ssl/ !/deps/rabbitmq_aws/ diff --git a/Makefile b/Makefile index d8636dd4b517..ce261b3c7470 100644 --- a/Makefile +++ b/Makefile @@ -524,6 +524,7 @@ TIER1_PLUGINS := \ rabbitmq_auth_backend_http \ rabbitmq_auth_backend_internal_loopback \ rabbitmq_auth_backend_ldap \ + rabbitmq_auth_backend_ldap_management \ rabbitmq_auth_backend_oauth2 \ rabbitmq_auth_mechanism_ssl \ rabbitmq_aws \ diff --git a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl index 0a571ede5e8c..1260b3253598 100644 --- a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl +++ b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl @@ -11,83 +11,8 @@ -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("amqp_client/include/amqp_client.hrl"). - --define(ALICE_NAME, "Alice"). --define(BOB_NAME, "Bob"). --define(CAROL_NAME, "Carol"). --define(PETER_NAME, "Peter"). --define(JIMMY_NAME, "Jimmy"). - --define(VHOST, "test"). - --define(ALICE, #amqp_params_network{username = <>, - password = <<"password">>, - virtual_host = <>}). - --define(BOB, #amqp_params_network{username = <>, - password = <<"password">>, - virtual_host = <>}). - --define(CAROL, #amqp_params_network{username = <>, - password = <<"password">>, - virtual_host = <>}). - --define(PETER, #amqp_params_network{username = <>, - password = <<"password">>, - virtual_host = <>}). - --define(JIMMY, #amqp_params_network{username = <>, - password = <<"password">>, - virtual_host = <>}). - --define(BASE_CONF_RABBIT, {rabbit, [{default_vhost, <<"test">>}]}). - -base_conf_ldap(LdapPort, IdleTimeout, PoolSize) -> - {rabbitmq_auth_backend_ldap, [{servers, ["localhost"]}, - {user_dn_pattern, "cn=${username},ou=People,dc=rabbitmq,dc=com"}, - {other_bind, anon}, - {use_ssl, false}, - {port, LdapPort}, - {idle_timeout, IdleTimeout}, - {pool_size, PoolSize}, - {log, true}, - {group_lookup_base, "ou=groups,dc=rabbitmq,dc=com"}, - {vhost_access_query, vhost_access_query_base()}, - {resource_access_query, - {for, [{resource, exchange, - {for, [{permission, configure, - {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} - }, - {permission, write, {constant, true}}, - {permission, read, - {match, {string, "${name}"}, - {string, "^xch-${username}-.*"}} - } - ]}}, - {resource, queue, - {for, [{permission, configure, - {match, {attribute, "${user_dn}", "description"}, - {string, "can-declare-queues"}} - }, - {permission, write, {constant, true}}, - {permission, read, - {'or', - [{'and', - [{equals, "${name}", "test1"}, - {equals, "${username}", "Alice"}]}, - {'and', - [{equals, "${name}", "test2"}, - {'not', {equals, "${username}", "Bob"}}]} - ]}} - ]}} - ]}}, - {topic_access_query, topic_access_query_base()}, - {tag_queries, [{monitor, {constant, true}}, - {administrator, {constant, false}}, - {management, {constant, false}}]} - ]}. - -%%-------------------------------------------------------------------- +-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl"). +-include_lib("rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl"). all() -> [ @@ -127,70 +52,18 @@ suite() -> init_per_suite(Config) -> rabbit_ct_helpers:log_environment(), - rabbit_ct_helpers:run_setup_steps(Config, [fun init_slapd/1]). + rabbit_ct_helpers:run_setup_steps(Config, [fun rabbit_ct_ldap_utils:init_slapd/1]). end_per_suite(Config) -> - rabbit_ct_helpers:run_teardown_steps(Config, [fun stop_slapd/1]). + rabbit_ct_helpers:run_teardown_steps(Config, [fun rabbit_ct_ldap_utils:stop_slapd/1]). init_per_group(Group, Config) -> - Config1 = rabbit_ct_helpers:set_config(Config, [ - {rmq_nodename_suffix, Group} - ]), - LdapPort = ?config(ldap_port, Config), - Config2 = rabbit_ct_helpers:merge_app_env(Config1, ?BASE_CONF_RABBIT), - Config3 = rabbit_ct_helpers:merge_app_env(Config2, - base_conf_ldap(LdapPort, - idle_timeout(Group), - pool_size(Group))), - rabbit_ldap_seed:seed({"localhost", LdapPort}), - Config4 = rabbit_ct_helpers:set_config(Config3, {ldap_port, LdapPort}), - - rabbit_ct_helpers:run_steps(Config4, - rabbit_ct_broker_helpers:setup_steps() ++ - rabbit_ct_client_helpers:setup_steps()). - -end_per_group(_, Config) -> - rabbit_ldap_seed:delete({"localhost", ?config(ldap_port, Config)}), - rabbit_ct_helpers:run_steps(Config, - rabbit_ct_client_helpers:teardown_steps() ++ - rabbit_ct_broker_helpers:teardown_steps()). - -init_slapd(Config) -> - DataDir = ?config(data_dir, Config), - PrivDir = ?config(priv_dir, Config), - TcpPort = 25389, - SlapdDir = filename:join([PrivDir, "openldap"]), - InitSlapd = filename:join([DataDir, "init-slapd.sh"]), - Cmd = [InitSlapd, SlapdDir, {"~b", [TcpPort]}], - case rabbit_ct_helpers:exec(Cmd) of - {ok, Stdout} -> - {match, [SlapdPid]} = re:run( - Stdout, - "^SLAPD_PID=([0-9]+)$", - [{capture, all_but_first, list}, - multiline]), - ct:pal(?LOW_IMPORTANCE, - "slapd(8) PID: ~ts~nslapd(8) listening on: ~b", - [SlapdPid, TcpPort]), - rabbit_ct_helpers:set_config(Config, - [{slapd_pid, SlapdPid}, - {ldap_port, TcpPort}]); - _ -> - _ = rabbit_ct_helpers:exec(["pkill", "-INT", "slapd"]), - {skip, "Failed to initialize slapd(8)"} - end. - -stop_slapd(Config) -> - SlapdPid = ?config(slapd_pid, Config), - Cmd = ["kill", "-INT", SlapdPid], - _ = rabbit_ct_helpers:exec(Cmd), - Config. + rabbit_ct_ldap_utils:init_per_group(Group, Config, + rabbit_ct_client_helpers:setup_steps()). -idle_timeout(with_idle_timeout) -> 2000; -idle_timeout(non_parallel_tests) -> infinity. - -pool_size(with_idle_timeout) -> 1; -pool_size(non_parallel_tests) -> 10. +end_per_group(Group, Config) -> + rabbit_ct_ldap_utils:end_per_group(Group, Config, + rabbit_ct_client_helpers:teardown_steps()). init_internal(Config) -> ok = control_action(Config, add_user, [?ALICE_NAME, ""]), @@ -206,6 +79,7 @@ end_internal(Config) -> ok = control_action(Config, delete_user, [?BOB_NAME]), ok = control_action(Config, delete_user, [?PETER_NAME]). + init_per_testcase(Testcase, Config) when Testcase == ldap_and_internal; Testcase == internal_followed_ldap_and_internal -> @@ -265,6 +139,11 @@ end_per_testcase(Testcase, Config) end_per_testcase(Testcase, Config) -> rabbit_ct_helpers:testcase_finished(Config, Testcase). +idle_timeout(Arg) -> + rabbit_ct_ldap_utils:idle_timeout(Arg). + +pool_size(Arg) -> + rabbit_ct_ldap_utils:pool_size(Arg). %% ------------------------------------------------------------------- %% Testsuite cases @@ -688,10 +567,7 @@ vhost_access_query_nested_groups_env() -> [{vhost_access_query, {in_group_nested, "cn=admins,ou=groups,dc=rabbitmq,dc=com"}}]. vhost_access_query_base_env() -> - [{vhost_access_query, vhost_access_query_base()}]. - -vhost_access_query_base() -> - {exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}. + [{vhost_access_query, rabbit_ct_ldap_utils:vhost_access_query_base()}]. resource_access_query_match_gh_100() -> [{resource_access_query, @@ -724,10 +600,7 @@ resource_access_query_match_query_and_re_query_are_strings() -> }]. topic_access_query_base_env() -> - [{topic_access_query, topic_access_query_base()}]. - -topic_access_query_base() -> - {constant, true}. + [{topic_access_query, rabbit_ct_ldap_utils:topic_access_query_base()}]. test_login(Config, {N, Env}, Login, FilterList, ResultFun) -> case lists:member(N, FilterList) of diff --git a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh deleted file mode 100755 index 2a9f9d3d4882..000000000000 --- a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh +++ /dev/null @@ -1,127 +0,0 @@ -#!/bin/sh -# vim:sw=4:et: - -set -ex - -readonly slapd_data_dir="$1" -readonly tcp_port="$2" - -readonly pidfile="$slapd_data_dir/slapd.pid" -readonly uri="ldap://localhost:$tcp_port" - -readonly binddn="cn=config" -readonly passwd=secret - -case "$(uname -s)" in - Linux) - if [ -x /usr/bin/slapd ] - then - readonly slapd=/usr/bin/slapd - elif [ -x /usr/sbin/slapd ] - then - readonly slapd=/usr/sbin/slapd - fi - - if [ -d /usr/lib/openldap ] - then - readonly modulepath=/usr/lib/openldap - elif [ -d /usr/lib/ldap ] - then - readonly modulepath=/usr/lib/ldap - fi - - if [ -d /etc/openldap/schema ] - then - readonly schema_dir=/etc/openldap/schema - elif [ -d /etc/ldap/schema ] - then - readonly schema_dir=/etc/ldap/schema - fi - ;; - FreeBSD) - readonly slapd=/usr/local/libexec/slapd - readonly modulepath=/usr/local/libexec/openldap - readonly schema_dir=/usr/local/etc/openldap/schema - ;; - *) - exit 1 - ;; -esac - -# -------------------------------------------------------------------- -# slapd(8) configuration + start -# -------------------------------------------------------------------- - -rm -rf "$slapd_data_dir" -mkdir -p "$slapd_data_dir" - -readonly conf_file="$slapd_data_dir/slapd.conf" -cat < "$conf_file" -include $schema_dir/core.schema -include $schema_dir/cosine.schema -include $schema_dir/nis.schema -include $schema_dir/inetorgperson.schema -pidfile $pidfile -modulepath $modulepath -loglevel 7 - -database config -rootdn "$binddn" -rootpw $passwd -EOF - -cat "$conf_file" - -readonly conf_dir="$slapd_data_dir/slapd.d" -mkdir -p "$conf_dir" - -# Start slapd(8). -"$slapd" \ - -f "$conf_file" \ - -F "$conf_dir" \ - -h "$uri" - -readonly auth="-x -D $binddn -w $passwd" - -# We wait for the server to start. -# shellcheck disable=SC2034 -for seconds in 1 2 3 4 5 6 7 8 9 10; do - # shellcheck disable=SC2086 - ldapsearch $auth -H "$uri" -LLL -b cn=config dn && break; - sleep 1 -done - -# -------------------------------------------------------------------- -# Load the example LDIFs for the testsuite. -# -------------------------------------------------------------------- - -tmp="$(cd "$(dirname "$0")" && pwd)" -readonly script_dir="$tmp" -readonly example_ldif_dir="$script_dir/../../example" -readonly example_data_dir="$slapd_data_dir/example" -mkdir -p "$example_data_dir" - -# We update the hard-coded database directory with the one we computed -# here, so the data is located inside the test directory. -# shellcheck disable=SC2086 -sed -E -e "s,^olcDbDirectory:.*,olcDbDirectory: $example_data_dir," \ - < "$example_ldif_dir/global.ldif" | \ - ldapadd $auth -H "$uri" - -# We remove the module path from the example LDIF as it was already -# configured. -# shellcheck disable=SC2086 -sed -E -e "s,^olcModulePath:.*,olcModulePath: $modulepath," \ - < "$example_ldif_dir/memberof_init.ldif" | \ - ldapadd $auth -H "$uri" - -# shellcheck disable=SC2086 -ldapmodify $auth -H "$uri" -f "$example_ldif_dir/refint_1.ldif" - -# shellcheck disable=SC2086 -ldapadd $auth -H "$uri" -f "$example_ldif_dir/refint_2.ldif" - -# shellcheck disable=SC2086 -ldapsearch $auth -H "$uri" -LLL -b cn=config dn - -echo SLAPD_PID="$(cat "$pidfile")" diff --git a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh new file mode 120000 index 000000000000..b082997ab42f --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh @@ -0,0 +1 @@ +../../../rabbitmq_ct_helpers/tools/init-slapd.sh \ No newline at end of file diff --git a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/ldif b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/ldif new file mode 120000 index 000000000000..ab890916d921 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/ldif @@ -0,0 +1 @@ +../../../rabbitmq_ct_helpers/tools/ldif \ No newline at end of file diff --git a/deps/rabbitmq_auth_backend_ldap_management/Makefile b/deps/rabbitmq_auth_backend_ldap_management/Makefile new file mode 100644 index 000000000000..663e6850fcd3 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap_management/Makefile @@ -0,0 +1,16 @@ +PROJECT = rabbitmq_auth_backend_ldap_management +PROJECT_DESCRIPTION = Management extension for the LDAP plugin + +define PROJECT_APP_EXTRA_KEYS + {broker_version_requirements, []} +endef + +LOCAL_DEPS = eldap public_key +DEPS = rabbit_common rabbit rabbitmq_management rabbitmq_auth_backend_ldap +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers + +DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk +DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk + +include ../../rabbitmq-components.mk +include ../../erlang.mk diff --git a/deps/rabbitmq_auth_backend_ldap_management/README.md b/deps/rabbitmq_auth_backend_ldap_management/README.md new file mode 100644 index 000000000000..0175da503023 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap_management/README.md @@ -0,0 +1,54 @@ +# RabbitMQ LDAP Management Plugin + +Provides an API to validate LDAP connection credentials and parameters. + +## Installing + +This plugin ships with RabbitMQ. Like all [plugins](https://www.rabbitmq.com/plugins.html), it must be enabled +before it can be used: + +``` +rabbitmq-plugins enable rabbitmq_auth_backend_ldap_management +``` + +## Usage + +When the plugin is enabled, an HTTP API will be available. + +### HTTP API + +The HTTP API adds an endpoint for validating LDAP credentials. + +#### `PUT /api/ldap/validate/simple-bind` + +Attempts an LDAP connection given the provided JSON configuration. + +**Example** + +Create a file called ``config.json`` similar to the following, replacing the parameter values as desired: + +```json +{ + "user_dn": "cn=foo,ou=baz,ou=bat,dc=rabbitmq,dc=com", + "password": "test1234", + "servers": ["localhost","my.ldap.com"], + "port": 389 +} +``` + +Once created, `PUT` the file to the HTTP API: + +```bash +curl -u guest:guest -v -X PUT -H 'Content-Type: application/json' -d @./config.json \ + http://localhost:15672/api/ldap/validate/simple-bind +``` + +Be sure to check the returned HTTP code and JSON to determine success! + +See [this test file](https://github.com/rabbitmq/rabbitmq-server/blob/main/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE.erl) for a comprehensive example of allowed configuration values to test LDAP connections. + +## License and Copyright + +Released under [the same license as RabbitMQ](https://www.rabbitmq.com/mpl.html). + +2007-2018 (c) 2007-2025 Broadcom. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. diff --git a/deps/rabbitmq_auth_backend_ldap_management/src/rabbit_auth_backend_ldap_mgmt.erl b/deps/rabbitmq_auth_backend_ldap_management/src/rabbit_auth_backend_ldap_mgmt.erl new file mode 100644 index 000000000000..a93a5e2cd56a --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap_management/src/rabbit_auth_backend_ldap_mgmt.erl @@ -0,0 +1,280 @@ +%% 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(rabbit_auth_backend_ldap_mgmt). + +-behaviour(rabbit_mgmt_extension). + +-export([dispatcher/0, web_ui/0]). + +-export([init/2, + content_types_accepted/2, + allowed_methods/2, + resource_exists/2, + is_authorized/2, + accept_content/2]). + + +-include_lib("kernel/include/logger.hrl"). +-include_lib("rabbitmq_web_dispatch/include/rabbitmq_web_dispatch_records.hrl"). + +dispatcher() -> [{"/ldap/validate/simple-bind", ?MODULE, []}]. + +web_ui() -> []. + +%%-------------------------------------------------------------------- + +init(Req, _Opts) -> + {cowboy_rest, rabbit_mgmt_cors:set_headers(Req, ?MODULE), #context{}}. + +content_types_accepted(ReqData, Context) -> + {[{'*', accept_content}], ReqData, Context}. + +allowed_methods(ReqData, Context) -> + {[<<"PUT">>, <<"OPTIONS">>], ReqData, Context}. + +resource_exists(ReqData, Context) -> + {true, ReqData, Context}. + +is_authorized(ReqData, Context) -> + rabbit_mgmt_util:is_authorized(ReqData, Context). + +accept_content(ReqData0, Context) -> + F = fun (_Values, BodyMap, ReqData1) -> + try + Port = safe_parse_int(maps:get(port, BodyMap, 389), "port"), + UseSsl = safe_parse_bool(maps:get(use_ssl, BodyMap, false), "use_ssl"), + UseStartTls = safe_parse_bool(maps:get(use_starttls, BodyMap, false), "use_starttls"), + Servers = safe_parse_servers(BodyMap), + UserDN = rabbit_data_coercion:to_unicode_charlist(maps:get(user_dn, BodyMap, <<"">>)), + Password = rabbit_data_coercion:to_unicode_charlist(maps:get(password, BodyMap, <<"">>)), + Options0 = [ + {port, Port}, + {timeout, 5000} + ], + {ok, Options1} = maybe_add_ssl_options(Options0, UseSsl, BodyMap), + case eldap:open(Servers, Options1) of + {ok, LDAP} -> + Result = case maybe_starttls(LDAP, UseStartTls, BodyMap) of + ok -> + case eldap:simple_bind(LDAP, UserDN, Password) of + ok -> + {true, ReqData1, Context}; + {error, invalidCredentials} -> + rabbit_mgmt_util:unprocessable_entity("invalid LDAP credentials: " + "authentication failure", + ReqData1, Context); + {error, unwillingToPerform} -> + rabbit_mgmt_util:unprocessable_entity("invalid LDAP credentials: " + "authentication failure", + ReqData1, Context); + {error, invalidDNSyntax} -> + rabbit_mgmt_util:unprocessable_entity("invalid LDAP credentials: " + "DN syntax invalid / too long", + ReqData1, Context); + {error, E} -> + Reason = unicode_format(E), + rabbit_mgmt_util:unprocessable_entity(Reason, ReqData1, Context) + end; + {error, tls_already_started} -> + rabbit_mgmt_util:unprocessable_entity("TLS configuration error: " + "cannot use StartTLS on an SSL connection " + "(use_ssl and use_starttls cannot both be true)", + ReqData1, Context); + Error -> + Reason = unicode_format(Error), + rabbit_mgmt_util:unprocessable_entity(Reason, ReqData1, Context) + end, + eldap:close(LDAP), + Result; + {error, E} -> + Reason = unicode_format("LDAP connection failed: ~tp " + "(servers: ~tp, user_dn: ~ts, password: ~s)", + [E, Servers, UserDN, format_password_for_logging(Password)]), + rabbit_mgmt_util:bad_request(Reason, ReqData1, Context) + end + catch throw:{bad_request, ErrMsg} -> + rabbit_mgmt_util:bad_request(ErrMsg, ReqData1, Context) + end + end, + rabbit_mgmt_util:with_decode([], ReqData0, Context, F). + +%%-------------------------------------------------------------------- + +maybe_starttls(_LDAP, false, _BodyMap) -> + ok; +maybe_starttls(LDAP, true, BodyMap) -> + {ok, TlsOptions} = tls_options(BodyMap), + eldap:start_tls(LDAP, TlsOptions, 5000). + +maybe_add_ssl_options(Options0, false, _BodyMap) -> + {ok, Options0}; +maybe_add_ssl_options(Options0, true, BodyMap) -> + case maps:is_key(ssl_options, BodyMap) of + false -> + {ok, Options0}; + true -> + Options1 = [{ssl, true} | Options0], + {ok, TlsOptions} = tls_options(BodyMap), + Options2 = [{sslopts, TlsOptions} | Options1], + {ok, Options2} + end. + +tls_options(BodyMap) when is_map_key(ssl_options, BodyMap) -> + SslOptionsMap = maps:get(ssl_options, BodyMap), + case is_map(SslOptionsMap) of + false -> + throw({bad_request, "ssl_options must be a map/object"}); + true -> + ok + end, + CaCertfile = maps:get(<<"cacertfile">>, SslOptionsMap, undefined), + CaCertPemData = maps:get(<<"cacert_pem_data">>, SslOptionsMap, undefined), + TlsOpts0 = case {CaCertfile, CaCertPemData} of + {undefined, undefined} -> + [{cacerts, public_key:cacerts_get()}]; + _ -> + [] + end, + %% NB: for some reason the "cacertfile" key isn't turned into an atom + TlsOpts1 = case CaCertfile of + undefined -> + TlsOpts0; + CaCertfile -> + [{cacertfile, CaCertfile} | TlsOpts0] + end, + TlsOpts2 = case CaCertPemData of + undefined -> + TlsOpts1; + CaCertPems when is_list(CaCertPems) -> + F0 = fun (P) -> + try + case public_key:pem_decode(P) of + [{'Certificate', CaCertDerEncoded, not_encrypted}] -> + {true, CaCertDerEncoded}; + [] -> + throw({bad_request, "invalid PEM data in cacert_pem_data: " + "no valid certificates found"}); + _Unexpected -> + throw({bad_request, "unexpected cacert_pem_data passed to " + "/ldap/validate/simple-bind ssl_options.cacerts"}) + end + catch + error:Reason -> + throw({bad_request, unicode_format("invalid PEM data in cacert_pem_data: ~tp", [Reason])}) + end + end, + CaCertsDerEncoded = lists:filtermap(F0, CaCertPems), + [{cacerts, CaCertsDerEncoded} | TlsOpts1]; + _ -> + TlsOpts1 + end, + TlsOpts3 = case maps:get(<<"verify">>, SslOptionsMap, undefined) of + undefined -> + TlsOpts2; + Verify -> + try + VerifyStr = to_unicode(Verify), + [{verify, binary_to_existing_atom(VerifyStr)} | TlsOpts2] + catch + error:badarg -> + throw({bad_request, "invalid verify option passed to " + "/ldap/validate/simple-bind ssl_options.verify"}) + end + end, + TlsOpts4 = case maps:get(<<"server_name_indication">>, SslOptionsMap, disable) of + disable -> + TlsOpts3; + SniValue -> + try + SniStr = unicode:characters_to_list(SniValue), + [{server_name_indication, SniStr} | TlsOpts3] + catch + error:badarg -> + throw({bad_request, "invalid server_name_indication: expected string"}); + error:_ -> + throw({bad_request, "invalid server_name_indication: expected string"}) + end + end, + TlsOpts5 = case maps:get(<<"depth">>, SslOptionsMap, undefined) of + undefined -> + TlsOpts4; + DepthValue -> + Depth = safe_parse_int(DepthValue, "ssl_options.depth"), + [{depth, Depth} | TlsOpts4] + end, + TlsOpts6 = case maps:get(<<"versions">>, SslOptionsMap, undefined) of + undefined -> + TlsOpts5; + VersionStrs when is_list(VersionStrs) -> + F1 = fun (VStr0) -> + try + VStr1 = to_unicode(VStr0), + {true, binary_to_existing_atom(VStr1)} + catch error:badarg -> + throw({bad_request, "invalid TLS version passed to " + "/ldap/validate/simple-bind ssl_options.versions"}) + end + end, + Versions = lists:filtermap(F1, VersionStrs), + [{versions, Versions} | TlsOpts5] + end, + TlsOpts7 = case to_unicode(maps:get(<<"ssl_hostname_verification">>, SslOptionsMap, undefined)) of + undefined -> + TlsOpts6; + <<"wildcard">> -> + [{customize_hostname_check, [{match_fun, public_key:pkix_verify_hostname_match_fun(https)}]} | TlsOpts6]; + _ -> + throw({bad_request, "invalid value passed to " + "/ldap/validate/simple-bind ssl_options.ssl_hostname_verification"}) + end, + {ok, TlsOpts7}; +tls_options(_BodyMap) -> + {ok, []}. + +to_unicode(undefined) -> + undefined; +to_unicode(Arg) when is_atom(Arg) -> + Arg; +to_unicode(Arg) -> + rabbit_data_coercion:to_utf8_binary(Arg). + +unicode_format(Arg) -> + rabbit_data_coercion:to_utf8_binary(io_lib:format("~tp", [Arg])). + +unicode_format(Format, Args) -> + rabbit_data_coercion:to_utf8_binary(io_lib:format(Format, Args)). + +format_password_for_logging(Password) -> + io_lib:format("[~p characters]", [string:length(Password)]). + +safe_parse_servers(BodyMap) when is_map(BodyMap) -> + safe_parse_servers(maps:get(servers, BodyMap, [])); +safe_parse_servers(Servers) when is_list(Servers) -> + [rabbit_data_coercion:to_unicode_charlist(S) || S <- Servers]; +safe_parse_servers(_) -> + []. + +safe_parse_int(Value, FieldName) -> + try + rabbit_mgmt_util:parse_int(Value) + catch + throw:{error, {not_integer, BadValue}} -> + Msg = unicode_format("invalid value for ~s: expected integer, got ~tp", + [FieldName, BadValue]), + throw({bad_request, Msg}) + end. + +safe_parse_bool(Value, FieldName) -> + try + rabbit_mgmt_util:parse_bool(Value) + catch + throw:{error, {not_boolean, BadValue}} -> + Msg = unicode_format("invalid value for ~s: expected boolean, got ~tp", + [FieldName, BadValue]), + throw({bad_request, Msg}) + end. diff --git a/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE.erl b/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE.erl new file mode 100644 index 000000000000..4dc87b71203e --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE.erl @@ -0,0 +1,501 @@ +%% 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(system_SUITE). + +-compile([export_all]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl"). +-include_lib("rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl"). + +-import(rabbit_mgmt_test_util, [http_put/4]). + +%%-------------------------------------------------------------------- + +all() -> + [{group, non_parallel_tests}]. + +groups() -> + [{non_parallel_tests, [], tests()}]. + +suite() -> + [{timetrap, {minutes, 2}}]. + +tests() -> + [validate_ldap_configuration_via_api]. + +%% ------------------------------------------------------------------- +%% Testsuite setup/teardown. +%% ------------------------------------------------------------------- + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config, [fun rabbit_ct_ldap_utils:init_slapd/1]). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config, [fun rabbit_ct_ldap_utils:stop_slapd/1]). + +init_per_group(Group, Config) -> + rabbit_ct_ldap_utils:init_per_group(Group, Config). + +end_per_group(Group, Config) -> + rabbit_ct_ldap_utils:end_per_group(Group, Config). + +init_per_testcase(validate_ldap_configuration_via_api = Testcase, Config) -> + _ = application:start(inets), + rabbit_ct_helpers:testcase_started(Config, Testcase); +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase). + +end_per_testcase(validate_ldap_configuration_via_api = Testcase, Config) -> + _ = application:stop(inets), + rabbit_ct_helpers:testcase_finished(Config, Testcase); +end_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_finished(Config, Testcase). + +%% ------------------------------------------------------------------- +%% Testsuite cases +%% ------------------------------------------------------------------- + +%% NOTE: +%% All strings in the JSON body MUST be binaries! +validate_ldap_configuration_via_api(Config) -> + CertsDir = ?config(rmq_certsdir, Config), + CaCertfile = filename:join([CertsDir, "testca", "cacert.pem"]), + + %% {user_dn_pattern, "cn=${username},ou=People,dc=rabbitmq,dc=com"}, + UserDNFmt = "cn=~ts,ou=People,dc=rabbitmq,dc=com", + AliceUserDN = rabbit_data_coercion:to_utf8_binary(io_lib:format(UserDNFmt, [?ALICE_NAME])), + InvalidUserDN = rabbit_data_coercion:to_utf8_binary(io_lib:format(UserDNFmt, ["NOBODY"])), + Password = rabbit_data_coercion:to_utf8_binary("password"), + + % NOTE: + % If you don't do this, the charlist "localhost" will be JSON-encoded as + % [108,111,99,97,108,104,111,115,116] + % which obviously isn't what will happen in the real world + LocalHost = rabbit_data_coercion:to_utf8_binary("localhost"), + EmptyString = <<"">>, + + LdapPort = ?config(ldap_port, Config), + LdapTlsPort = ?config(ldap_tls_port, Config), + + %% NB: bad resource name + http_put(Config, "/ldap/validate/bad-bind-name", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort + }, ?METHOD_NOT_ALLOWED), + %% Invalid JSON should return 400 Bad Request + rabbit_mgmt_test_util:http_put_raw(Config, "/ldap/validate/simple-bind", + "{invalid json", ?BAD_REQUEST), + + %% HTTP Method coverage tests + %% GET method - should return 405 Method Not Allowed + ?assertMatch({ok, {{_, ?METHOD_NOT_ALLOWED, _}, _Headers, _ResBody}}, + rabbit_mgmt_test_util:req(Config, 0, get, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")])), + + %% HEAD method - should return 405 Method Not Allowed (same as GET) + ?assertMatch({ok, {{_, ?METHOD_NOT_ALLOWED, _}, _Headers, _ResBody}}, + rabbit_mgmt_test_util:req(Config, 0, head, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")])), + + %% POST method - should return 405 Method Not Allowed + ?assertMatch({ok, {{_, ?METHOD_NOT_ALLOWED, _}, _Headers, _ResBody}}, + rabbit_mgmt_test_util:req(Config, 0, post, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")], "{}")), + + %% DELETE method - should return 405 Method Not Allowed + ?assertMatch({ok, {{_, ?METHOD_NOT_ALLOWED, _}, _Headers, _ResBody}}, + rabbit_mgmt_test_util:req(Config, 0, delete, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")])), + + %% OPTIONS method - should return 200 with Allow header showing only PUT, OPTIONS + {ok, {{_, OptionsCode, _}, OptionsHeaders, _OptionsResBody}} = + rabbit_mgmt_test_util:req(Config, 0, options, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")]), + ?assertEqual(?OK, OptionsCode), + AllowHeader = proplists:get_value("allow", OptionsHeaders), + ?assert(string:str(string:to_upper(AllowHeader), "PUT") > 0), + ?assert(string:str(string:to_upper(AllowHeader), "OPTIONS") > 0), + %% Should NOT contain GET or HEAD + ?assertEqual(0, string:str(string:to_upper(AllowHeader), "GET")), + ?assertEqual(0, string:str(string:to_upper(AllowHeader), "HEAD")), + + %% Missing required fields tests + %% Empty servers array - connection failure (400) + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [], + 'port' => LdapPort + }, ?BAD_REQUEST), + + %% Missing servers field entirely - defaults to [], same as above (400) + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'port' => LdapPort + }, ?BAD_REQUEST), + + %% Missing user_dn field entirely - empty DN fails credential validation (422) + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort + }, ?UNPROCESSABLE_ENTITY), + + %% Missing password field entirely - empty password fails credential validation (422) + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'servers' => [LocalHost], + 'port' => LdapPort + }, ?UNPROCESSABLE_ENTITY), + + %% Invalid field values tests + %% Invalid port - string instead of number + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => "not_a_number" + }, ?BAD_REQUEST), + + %% Invalid port - negative number + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => -1 + }, ?BAD_REQUEST), + + %% Invalid boolean - string instead of boolean + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort, + 'use_ssl' => <<"maybe">> + }, ?BAD_REQUEST), + + %% Invalid servers - non-list value + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => <<"not_a_list">>, + 'port' => LdapPort + }, ?BAD_REQUEST), + + %% Network/Infrastructure scenarios + %% Non-existent server + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [<<"nonexistent.example.com">>], + 'port' => LdapPort + }, ?BAD_REQUEST), + + %% Invalid hostname format + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [<<"not..a..valid..hostname">>], + 'port' => LdapPort + }, ?BAD_REQUEST), + + %% Edge case credentials tests + %% Empty password - should be 422 (credential validation failure) + {ok, {{_, 422, _}, _Headers1, EmptyPasswordBody}} = + rabbit_mgmt_test_util:req(Config, 0, put, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")], + rabbit_mgmt_test_util:format_for_upload(#{ + 'user_dn' => AliceUserDN, + 'password' => EmptyString, + 'servers' => [LocalHost], + 'port' => LdapPort + })), + EmptyPasswordJson = rabbit_json:decode(EmptyPasswordBody), + ?assertEqual(<<"unprocessable_entity">>, maps:get(<<"error">>, EmptyPasswordJson)), + ?assertEqual(<<"anonymous_auth">>, maps:get(<<"reason">>, EmptyPasswordJson)), + + %% Empty user DN - should be 422 (credential validation failure) + {ok, {{_, 422, _}, _Headers2, EmptyUserDnBody}} = + rabbit_mgmt_test_util:req(Config, 0, put, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")], + rabbit_mgmt_test_util:format_for_upload(#{ + 'user_dn' => EmptyString, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort + })), + EmptyUserDnJson = rabbit_json:decode(EmptyUserDnBody), + ?assertEqual(<<"unprocessable_entity">>, maps:get(<<"error">>, EmptyUserDnJson)), + ?assertEqual(<<"anonymous_auth">>, maps:get(<<"reason">>, EmptyUserDnJson)), + + %% Very long user DN (test size limits) + {ok, {{_, 422, _}, _Headers3, LongUserDnBody}} = + rabbit_mgmt_test_util:req(Config, 0, put, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")], + rabbit_mgmt_test_util:format_for_upload(#{ + 'user_dn' => binary:copy(<<"x">>, 10000), + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort + })), + LongUserDnJson = rabbit_json:decode(LongUserDnBody), + ?assertEqual(<<"unprocessable_entity">>, maps:get(<<"error">>, LongUserDnJson)), + ?assertEqual(<<"invalid LDAP credentials: DN syntax invalid / too long">>, + maps:get(<<"reason">>, LongUserDnJson)), + + %% Very long password (test size limits) + {ok, {{_, 422, _}, _Headers4, LongPasswordBody}} = + rabbit_mgmt_test_util:req(Config, 0, put, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")], + rabbit_mgmt_test_util:format_for_upload(#{ + 'user_dn' => AliceUserDN, + 'password' => binary:copy(<<"x">>, 10000), + 'servers' => [LocalHost], + 'port' => LdapPort + })), + LongPasswordJson = rabbit_json:decode(LongPasswordBody), + ?assertEqual(<<"unprocessable_entity">>, maps:get(<<"error">>, LongPasswordJson)), + ?assertEqual(<<"invalid LDAP credentials: authentication failure">>, + maps:get(<<"reason">>, LongPasswordJson)), + + %% SSL/TLS Edge Cases + %% Both use_ssl and use_starttls set to true - TLS configuration error + {ok, {{_, 422, _}, _Headers5, BothTlsBody}} = + rabbit_mgmt_test_util:req(Config, 0, put, "/ldap/validate/simple-bind", + [rabbit_mgmt_test_util:auth_header("guest", "guest")], + rabbit_mgmt_test_util:format_for_upload(#{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'use_starttls' => true, + 'ssl_options' => #{ + 'cacertfile' => CaCertfile + } + })), + BothTlsJson = rabbit_json:decode(BothTlsBody), + ?assertEqual(<<"unprocessable_entity">>, maps:get(<<"error">>, BothTlsJson)), + ?assertEqual(<<"TLS configuration error: cannot use StartTLS on an SSL connection (use_ssl and use_starttls cannot both be true)">>, + maps:get(<<"reason">>, BothTlsJson)), + + %% Invalid certificate file path + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'cacertfile' => <<"/nonexistent/path/cert.pem">> + } + }, ?BAD_REQUEST), + + %% Invalid PEM data - should now return 400 Bad Request + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'cacert_pem_data' => [<<"not-valid-pem-data">>] + } + }, ?BAD_REQUEST), + + %% Invalid SSL options structure - not a map + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => <<"not_a_map">> + }, ?BAD_REQUEST), + + %% Invalid TLS versions + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'versions' => [<<"invalid_version">>, <<"tlsv1.2">>], + 'cacertfile' => CaCertfile + } + }, ?BAD_REQUEST), + + %% Invalid verify option + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'verify' => <<"invalid_verify_option">>, + 'cacertfile' => CaCertfile + } + }, ?BAD_REQUEST), + + %% Invalid depth value - string instead of integer + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'depth' => <<"not_a_number">>, + 'cacertfile' => CaCertfile + } + }, ?BAD_REQUEST), + + %% Invalid server_name_indication - integer instead of string + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'server_name_indication' => 123, + 'cacertfile' => CaCertfile + } + }, ?BAD_REQUEST), + + %% Invalid server_name_indication - boolean instead of string + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'server_name_indication' => true, + 'cacertfile' => CaCertfile + } + }, ?BAD_REQUEST), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort + }, ?NO_CONTENT), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => InvalidUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort + }, ?UNPROCESSABLE_ENTITY), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'cacertfile' => CaCertfile + } + }, ?NO_CONTENT), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'server_name_indication' => LocalHost, + 'cacertfile' => CaCertfile + } + }, ?NO_CONTENT), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapPort, + 'use_ssl' => false, + 'use_starttls' => true, + 'ssl_options' => #{ + 'server_name_indication' => LocalHost, + 'cacertfile' => CaCertfile + } + }, ?NO_CONTENT), + {ok, CaCertfileContent} = file:read_file(CaCertfile), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'versions' => [<<"tlsv1.2">>, <<"tlsv1.3">>], + 'depth' => 8, + 'verify' => <<"verify_peer">>, + 'cacert_pem_data' => [CaCertfileContent] + } + }, ?NO_CONTENT), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'versions' => [<<"tlsfoobar">>, <<"tlsv1.3">>], + 'depth' => 8, + 'verify' => <<"verify_peer">>, + 'cacert_pem_data' => [CaCertfileContent, CaCertfileContent] + } + }, ?BAD_REQUEST), + http_put(Config, "/ldap/validate/simple-bind", + #{ + 'user_dn' => AliceUserDN, + 'password' => Password, + 'servers' => [LocalHost], + 'port' => LdapTlsPort, + 'use_ssl' => true, + 'ssl_options' => #{ + 'verify' => <<"verify_peer">>, + 'cacertfile' => CaCertfile, + 'ssl_hostname_verification' => <<"wildcard">> + } + }, ?NO_CONTENT). diff --git a/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/init-slapd.sh b/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/init-slapd.sh new file mode 120000 index 000000000000..b082997ab42f --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/init-slapd.sh @@ -0,0 +1 @@ +../../../rabbitmq_ct_helpers/tools/init-slapd.sh \ No newline at end of file diff --git a/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/ldif b/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/ldif new file mode 120000 index 000000000000..ab890916d921 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap_management/test/system_SUITE_data/ldif @@ -0,0 +1 @@ +../../../rabbitmq_ct_helpers/tools/ldif \ No newline at end of file diff --git a/deps/rabbitmq_ct_helpers/Makefile b/deps/rabbitmq_ct_helpers/Makefile index 80eb0310c9cb..fad090ccadb5 100644 --- a/deps/rabbitmq_ct_helpers/Makefile +++ b/deps/rabbitmq_ct_helpers/Makefile @@ -2,8 +2,7 @@ PROJECT = rabbitmq_ct_helpers PROJECT_DESCRIPTION = Common Test helpers for RabbitMQ DEPS = rabbit_common amqp10_common rabbitmq_stream_common proper inet_tcp_proxy meck -LOCAL_DEPS = common_test eunit inets -#TEST_DEPS = rabbit +LOCAL_DEPS = common_test eunit inets eldap # We are calling one function from 'rabbit' so we need it in the PLT. # But really this should be a full dependency; or we don't use the diff --git a/deps/rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl b/deps/rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl new file mode 100644 index 000000000000..0cca867993e0 --- /dev/null +++ b/deps/rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl @@ -0,0 +1,36 @@ +%% 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. +%% + +-define(ALICE_NAME, "Alice"). +-define(BOB_NAME, "Bob"). +-define(CAROL_NAME, "Carol"). +-define(PETER_NAME, "Peter"). +-define(JIMMY_NAME, "Jimmy"). + +-define(VHOST, "test"). + +-define(ALICE, #amqp_params_network{username = <>, + password = <<"password">>, + virtual_host = <>}). + +-define(BOB, #amqp_params_network{username = <>, + password = <<"password">>, + virtual_host = <>}). + +-define(CAROL, #amqp_params_network{username = <>, + password = <<"password">>, + virtual_host = <>}). + +-define(PETER, #amqp_params_network{username = <>, + password = <<"password">>, + virtual_host = <>}). + +-define(JIMMY, #amqp_params_network{username = <>, + password = <<"password">>, + virtual_host = <>}). + +-define(BASE_CONF_RABBIT, {rabbit, [{default_vhost, <<"test">>}]}). diff --git a/deps/rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl b/deps/rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl index 857cc89467c7..88565b0781c7 100644 --- a/deps/rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl +++ b/deps/rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl @@ -8,6 +8,7 @@ -define(BAD_REQUEST, 400). -define(NOT_AUTHORISED, 401). -define(METHOD_NOT_ALLOWED, 405). +-define(UNPROCESSABLE_ENTITY, 422). %%-define(NOT_FOUND, 404). Defined for AMQP by amqp_client.hrl (as 404) %% httpc seems to get racy when using HTTP 1.1 -define(HTTPC_OPTS, [{version, "HTTP/1.0"}, {autoredirect, false}]). diff --git a/deps/rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl b/deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_seed.erl similarity index 99% rename from deps/rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl rename to deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_seed.erl index 350d8300ef24..444052f96af1 100644 --- a/deps/rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl +++ b/deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_seed.erl @@ -5,7 +5,7 @@ %% Copyright (c) 2007-2025 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. %% --module(rabbit_ldap_seed). +-module(rabbit_ct_ldap_seed). -include_lib("stdlib/include/assert.hrl"). diff --git a/deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_utils.erl b/deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_utils.erl new file mode 100644 index 000000000000..214b5fae14c0 --- /dev/null +++ b/deps/rabbitmq_ct_helpers/src/rabbit_ct_ldap_utils.erl @@ -0,0 +1,149 @@ +%% 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(rabbit_ct_ldap_utils). + +-export([vhost_access_query_base/0, + topic_access_query_base/0, + init_per_group/2, init_per_group/3, + end_per_group/2, end_per_group/3, + idle_timeout/1, + pool_size/1, + init_slapd/1, + stop_slapd/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("rabbitmq_ct_helpers/include/rabbit_ldap_test.hrl"). + +base_conf_ldap(LdapPort, IdleTimeout, PoolSize) -> + {rabbitmq_auth_backend_ldap, [{servers, ["localhost"]}, + {user_dn_pattern, "cn=${username},ou=People,dc=rabbitmq,dc=com"}, + {other_bind, anon}, + {use_ssl, false}, + {port, LdapPort}, + {idle_timeout, IdleTimeout}, + {pool_size, PoolSize}, + {log, true}, + {group_lookup_base, "ou=groups,dc=rabbitmq,dc=com"}, + {vhost_access_query, vhost_access_query_base()}, + {resource_access_query, + {for, [{resource, exchange, + {for, [{permission, configure, + {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} + }, + {permission, write, {constant, true}}, + {permission, read, + {match, {string, "${name}"}, + {string, "^xch-${username}-.*"}} + } + ]}}, + {resource, queue, + {for, [{permission, configure, + {match, {attribute, "${user_dn}", "description"}, + {string, "can-declare-queues"}} + }, + {permission, write, {constant, true}}, + {permission, read, + {'or', + [{'and', + [{equals, "${name}", "test1"}, + {equals, "${username}", "Alice"}]}, + {'and', + [{equals, "${name}", "test2"}, + {'not', {equals, "${username}", "Bob"}}]} + ]}} + ]}} + ]}}, + {topic_access_query, topic_access_query_base()}, + {tag_queries, [{monitor, {constant, true}}, + {administrator, {constant, false}}, + {management, {constant, false}}]} + ]}. + +vhost_access_query_base() -> + {exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}. + +topic_access_query_base() -> + {constant, true}. + +init_per_group(Group, Config) -> + init_per_group(Group, Config, []). + +init_per_group(Group, Config, ExtraSteps) -> + Config1 = rabbit_ct_helpers:set_config(Config, [ + {rmq_nodename_suffix, Group} + ]), + LdapPort = ?config(ldap_port, Config), + Config2 = rabbit_ct_helpers:merge_app_env(Config1, ?BASE_CONF_RABBIT), + Config3 = rabbit_ct_helpers:merge_app_env(Config2, + base_conf_ldap(LdapPort, idle_timeout(Group), pool_size(Group))), + rabbit_ct_ldap_seed:seed({"localhost", LdapPort}), + Config4 = rabbit_ct_helpers:set_config(Config3, {ldap_port, LdapPort}), + + Steps = rabbit_ct_broker_helpers:setup_steps() ++ ExtraSteps, + rabbit_ct_helpers:run_steps(Config4, Steps). + +end_per_group(Arg, Config) -> + end_per_group(Arg, Config, []). + +end_per_group(_Arg, Config, ExtraSteps) -> + rabbit_ct_ldap_seed:delete({"localhost", ?config(ldap_port, Config)}), + Steps = rabbit_ct_broker_helpers:teardown_steps() ++ ExtraSteps, + rabbit_ct_helpers:run_steps(Config, Steps). + +idle_timeout(with_idle_timeout) -> 2000; +idle_timeout(non_parallel_tests) -> infinity. + +pool_size(with_idle_timeout) -> 1; +pool_size(non_parallel_tests) -> 10. + +init_slapd(Config) -> + DataDir = ?config(data_dir, Config), + PrivDir = ?config(priv_dir, Config), + CertsDir = ?config(rmq_certsdir, Config), + CaCertfile = filename:join([CertsDir, "testca", "cacert.pem"]), + ServerCertfile = filename:join([CertsDir, "server", "cert.pem"]), + ServerKeyfile = filename:join([CertsDir, "server", "key.pem"]), + TcpPort = 25389, + TlsPort = 25689, + SlapdDir = filename:join([PrivDir, "openldap"]), + %% + InitSlapd = filename:join([DataDir, "init-slapd.sh"]), + Cmd = [ + InitSlapd, + SlapdDir, + {"~b", [TcpPort]}, + {"~b", [TlsPort]}, + CaCertfile, + ServerCertfile, + ServerKeyfile + ], + case rabbit_ct_helpers:exec(Cmd) of + {ok, Stdout} -> + {match, [SlapdPid]} = re:run( + Stdout, + "^SLAPD_PID=([0-9]+)$", + [{capture, all_but_first, list}, + multiline]), + ct:pal(?LOW_IMPORTANCE, + "slapd(8) PID: ~ts~nslapd(8) listening on: ~b", + [SlapdPid, TcpPort]), + rabbit_ct_helpers:set_config(Config, + [{slapd_pid, SlapdPid}, + {ldap_port, TcpPort}, + {ldap_tls_port, TlsPort}]); + _ -> + _ = rabbit_ct_helpers:exec(["pkill", "-INT", "slapd"]), + {skip, "Failed to initialize slapd(8)"} + end. + +stop_slapd(Config) -> + SlapdPid = ?config(slapd_pid, Config), + Cmd = ["kill", "-INT", SlapdPid], + _ = rabbit_ct_helpers:exec(Cmd), + Config. + diff --git a/deps/rabbitmq_ct_helpers/tools/init-slapd.sh b/deps/rabbitmq_ct_helpers/tools/init-slapd.sh new file mode 100755 index 000000000000..b335d946e1ff --- /dev/null +++ b/deps/rabbitmq_ct_helpers/tools/init-slapd.sh @@ -0,0 +1,136 @@ +#!/bin/sh +# vim:sw=4:et: + +set -eux + +readonly slapd_data_dir="$1" +readonly tcp_port="$2" +readonly tls_port="$3" +readonly cacertfile="$4" +readonly server_certfile="$5" +readonly server_keyfile="$6" + +readonly pidfile="$slapd_data_dir/slapd.pid" +readonly tcp_uri="ldap://localhost:$tcp_port" +readonly tls_uri="ldaps://localhost:$tls_port" + +readonly binddn="cn=config" +readonly passwd=secret + +case "$(uname -s)" in + Linux) + if [ -x /usr/bin/slapd ] + then + readonly slapd=/usr/bin/slapd + elif [ -x /usr/sbin/slapd ] + then + readonly slapd=/usr/sbin/slapd + fi + + if [ -d /usr/lib/openldap ] + then + readonly modulepath=/usr/lib/openldap + elif [ -d /usr/lib/ldap ] + then + readonly modulepath=/usr/lib/ldap + fi + + if [ -d /etc/openldap/schema ] + then + readonly schema_dir=/etc/openldap/schema + elif [ -d /etc/ldap/schema ] + then + readonly schema_dir=/etc/ldap/schema + fi + ;; + FreeBSD) + readonly slapd=/usr/local/libexec/slapd + readonly modulepath=/usr/local/libexec/openldap + readonly schema_dir=/usr/local/etc/openldap/schema + ;; + *) + exit 1 + ;; +esac + +# -------------------------------------------------------------------- +# slapd(8) configuration + start +# -------------------------------------------------------------------- + +rm -rf "$slapd_data_dir" +mkdir -p "$slapd_data_dir" + +readonly conf_file="$slapd_data_dir/slapd.conf" +cat < "$conf_file" +include $schema_dir/core.schema +include $schema_dir/cosine.schema +include $schema_dir/nis.schema +include $schema_dir/inetorgperson.schema +pidfile $pidfile +modulepath $modulepath +loglevel 7 + +database config +rootdn "$binddn" +rootpw $passwd + +TLSCACertificateFile $cacertfile +TLSCertificateFile $server_certfile +TLSCertificateKeyFile $server_keyfile +EOF + +cat "$conf_file" + +readonly conf_dir="$slapd_data_dir/slapd.d" +mkdir -p "$conf_dir" + +# Start slapd(8). +"$slapd" \ + -f "$conf_file" \ + -F "$conf_dir" \ + -h "$tcp_uri $tls_uri" + +readonly auth="-x -D $binddn -w $passwd" + +# We wait for the server to start. +# shellcheck disable=SC2034 +for seconds in 1 2 3 4 5 6 7 8 9 10; do + # shellcheck disable=SC2086 + ldapsearch $auth -H "$tcp_uri" -LLL -b cn=config dn && break; + sleep 1 +done + +# -------------------------------------------------------------------- +# Load the example LDIFs for the testsuite. +# -------------------------------------------------------------------- + +tmp="$(cd "$(dirname "$0")" && pwd)" +readonly script_dir="$tmp" +readonly example_ldif_dir="$script_dir/ldif" +readonly example_data_dir="$slapd_data_dir/ldif-data" +mkdir -p "$example_data_dir" + +# We update the hard-coded database directory with the one we computed +# here, so the data is located inside the test directory. +# shellcheck disable=SC2086 +sed -E -e "s,^olcDbDirectory:.*,olcDbDirectory: $example_data_dir," \ + < "$example_ldif_dir/global.ldif" | \ + ldapadd $auth -H "$tcp_uri" + +# We remove the module path from the example LDIF as it was already +# configured. +# shellcheck disable=SC2086 +sed -E -e "s,^olcModulePath:.*,olcModulePath: $modulepath," \ + < "$example_ldif_dir/memberof_init.ldif" | \ + ldapadd $auth -H "$tcp_uri" + +# shellcheck disable=SC2086 +ldapmodify $auth -H "$tcp_uri" -f "$example_ldif_dir/refint_1.ldif" + +# shellcheck disable=SC2086 +ldapadd $auth -H "$tcp_uri" -f "$example_ldif_dir/refint_2.ldif" + +# shellcheck disable=SC2086 +ldapsearch $auth -H "$tcp_uri" -LLL -b cn=config dn + +echo SLAPD_PID="$(cat "$pidfile")" diff --git a/deps/rabbitmq_auth_backend_ldap/example/README.md b/deps/rabbitmq_ct_helpers/tools/ldif/README.md similarity index 100% rename from deps/rabbitmq_auth_backend_ldap/example/README.md rename to deps/rabbitmq_ct_helpers/tools/ldif/README.md diff --git a/deps/rabbitmq_auth_backend_ldap/example/global.ldif b/deps/rabbitmq_ct_helpers/tools/ldif/global.ldif similarity index 100% rename from deps/rabbitmq_auth_backend_ldap/example/global.ldif rename to deps/rabbitmq_ct_helpers/tools/ldif/global.ldif diff --git a/deps/rabbitmq_auth_backend_ldap/example/memberof_init.ldif b/deps/rabbitmq_ct_helpers/tools/ldif/memberof_init.ldif similarity index 100% rename from deps/rabbitmq_auth_backend_ldap/example/memberof_init.ldif rename to deps/rabbitmq_ct_helpers/tools/ldif/memberof_init.ldif diff --git a/deps/rabbitmq_auth_backend_ldap/example/refint_1.ldif b/deps/rabbitmq_ct_helpers/tools/ldif/refint_1.ldif similarity index 100% rename from deps/rabbitmq_auth_backend_ldap/example/refint_1.ldif rename to deps/rabbitmq_ct_helpers/tools/ldif/refint_1.ldif diff --git a/deps/rabbitmq_auth_backend_ldap/example/refint_2.ldif b/deps/rabbitmq_ct_helpers/tools/ldif/refint_2.ldif similarity index 100% rename from deps/rabbitmq_auth_backend_ldap/example/refint_2.ldif rename to deps/rabbitmq_ct_helpers/tools/ldif/refint_2.ldif diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl index 54fef24144d5..7448b0cc7424 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_util.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_util.erl @@ -18,8 +18,9 @@ is_authorized_vhost_visible_for_monitoring/2, is_authorized_global_parameters/2]). -export([user/1]). --export([bad_request/3, service_unavailable/3, bad_request_exception/4, +-export([bad_request/3, service_unavailable/3, not_authorised/3, bad_request_exception/4, internal_server_error/3, internal_server_error/4, precondition_failed/3, + unprocessable_entity/3, id/2, parse_bool/1, parse_int/1, redirect_to_home/3]). -export([with_decode/4, not_found/3]). -export([with_channel/4, with_channel/5]). @@ -675,10 +676,12 @@ a2b(B) -> B. bad_request(Reason, ReqData, Context) -> halt_response(400, bad_request, Reason, ReqData, Context). +unprocessable_entity(Reason, ReqData, Context) -> + halt_response(422, unprocessable_entity, Reason, ReqData, Context). + service_unavailable(Reason, ReqData, Context) -> halt_response(503, service_unavailable, Reason, ReqData, Context). - not_authorised(Reason, ReqData, Context) -> rabbit_web_dispatch_access_control:not_authorised(Reason, ReqData, Context). diff --git a/mk/github-actions.mk b/mk/github-actions.mk index 58c1e76d6348..9954917354e7 100644 --- a/mk/github-actions.mk +++ b/mk/github-actions.mk @@ -7,6 +7,7 @@ VENDORED_COMPONENTS = rabbit_common \ rabbitmq_auth_backend_cache \ rabbitmq_auth_backend_http \ rabbitmq_auth_backend_ldap \ + rabbitmq_auth_backend_ldap_management \ rabbitmq_auth_backend_oauth2 \ rabbitmq_auth_mechanism_ssl \ rabbitmq_aws \ diff --git a/plugins.mk b/plugins.mk index 6fb3a72389e7..72ed09d0c1cf 100644 --- a/plugins.mk +++ b/plugins.mk @@ -10,6 +10,7 @@ PLUGINS := rabbitmq_amqp1_0 \ rabbitmq_auth_backend_http \ rabbitmq_auth_backend_internal_loopback \ rabbitmq_auth_backend_ldap \ + rabbitmq_auth_backend_ldap_management \ rabbitmq_auth_backend_oauth2 \ rabbitmq_auth_mechanism_ssl \ rabbitmq_consistent_hash_exchange \ diff --git a/rabbitmq-components.mk b/rabbitmq-components.mk index 112c8b06450d..d4aeb344ad90 100644 --- a/rabbitmq-components.mk +++ b/rabbitmq-components.mk @@ -80,6 +80,7 @@ RABBITMQ_BUILTIN = \ rabbitmq_auth_backend_http \ rabbitmq_auth_backend_internal_loopback \ rabbitmq_auth_backend_ldap \ + rabbitmq_auth_backend_ldap_management \ rabbitmq_auth_backend_oauth2 \ rabbitmq_auth_mechanism_ssl \ rabbitmq_aws \