Merge pull request #14651 from rabbitmq/revert-14414-lukebakken/ldap-validation-api
Trigger a 4.2.x alpha release build / trigger_alpha_build (push) Waiting to run
Details
Test (make) / Build and Xref (1.18, 26) (push) Waiting to run
Details
Test (make) / Build and Xref (1.18, 27) (push) Waiting to run
Details
Test (make) / Build and Xref (1.18, 28) (push) Waiting to run
Details
Test (make) / Test (1.18, 28, khepri) (push) Waiting to run
Details
Test (make) / Test (1.18, 28, mnesia) (push) Waiting to run
Details
Test (make) / Test mixed clusters (1.18, 28, khepri) (push) Waiting to run
Details
Test (make) / Test mixed clusters (1.18, 28, mnesia) (push) Waiting to run
Details
Test (make) / Type check (1.18, 28) (push) Waiting to run
Details
Test Authentication/Authorization backends via mutiple messaging protocols / selenium (chrome, 1.17.3, 27.3) (push) Has been cancelled
Details
Test Management UI with Selenium / selenium (chrome, 1.17.3, 27.3) (push) Has been cancelled
Details
Test Authentication/Authorization backends via mutiple messaging protocols / summary-selenium (push) Has been cancelled
Details
Trigger a 4.2.x alpha release build / trigger_alpha_build (push) Waiting to run
Details
Test (make) / Build and Xref (1.18, 26) (push) Waiting to run
Details
Test (make) / Build and Xref (1.18, 27) (push) Waiting to run
Details
Test (make) / Build and Xref (1.18, 28) (push) Waiting to run
Details
Test (make) / Test (1.18, 28, khepri) (push) Waiting to run
Details
Test (make) / Test (1.18, 28, mnesia) (push) Waiting to run
Details
Test (make) / Test mixed clusters (1.18, 28, khepri) (push) Waiting to run
Details
Test (make) / Test mixed clusters (1.18, 28, mnesia) (push) Waiting to run
Details
Test (make) / Type check (1.18, 28) (push) Waiting to run
Details
Test Authentication/Authorization backends via mutiple messaging protocols / selenium (chrome, 1.17.3, 27.3) (push) Has been cancelled
Details
Test Management UI with Selenium / selenium (chrome, 1.17.3, 27.3) (push) Has been cancelled
Details
Test Authentication/Authorization backends via mutiple messaging protocols / summary-selenium (push) Has been cancelled
Details
Revert "Implement LDAP credentials validation via HTTP API"
This commit is contained in:
commit
a95c3192fc
|
@ -35,7 +35,7 @@ define PROJECT_APP_EXTRA_KEYS
|
|||
endef
|
||||
|
||||
LOCAL_DEPS = eldap public_key
|
||||
DEPS = rabbit_common rabbit rabbitmq_management
|
||||
DEPS = rabbit_common rabbit
|
||||
TEST_DEPS = ct_helper rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp_client
|
||||
dep_ct_helper = git https://github.com/extend/ct_helper.git master
|
||||
|
||||
|
|
|
@ -1,267 +0,0 @@
|
|||
%% 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 = maps:get(servers, BodyMap, []),
|
||||
UserDN = maps:get(user_dn, BodyMap, <<"">>),
|
||||
Password = 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 = unicode:characters_to_list(Verify),
|
||||
[{verify, list_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 (VStr) ->
|
||||
try
|
||||
{true, list_to_existing_atom(VStr)}
|
||||
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 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, []}.
|
||||
|
||||
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(<<>>) ->
|
||||
"[empty]";
|
||||
format_password_for_logging(Password) ->
|
||||
io_lib:format("[~p characters]", [string:length(Password)]).
|
||||
|
||||
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.
|
|
@ -11,9 +11,6 @@
|
|||
-include_lib("common_test/include/ct.hrl").
|
||||
-include_lib("eunit/include/eunit.hrl").
|
||||
-include_lib("amqp_client/include/amqp_client.hrl").
|
||||
-include_lib("rabbitmq_ct_helpers/include/rabbit_mgmt_test.hrl").
|
||||
|
||||
-import(rabbit_mgmt_test_util, [http_put/4]).
|
||||
|
||||
-define(ALICE_NAME, "Alice").
|
||||
-define(BOB_NAME, "Bob").
|
||||
|
@ -100,7 +97,6 @@ all() ->
|
|||
|
||||
groups() ->
|
||||
Tests = [
|
||||
validate_ldap_configuration_via_api,
|
||||
purge_connection,
|
||||
ldap_only,
|
||||
ldap_and_internal,
|
||||
|
@ -162,23 +158,10 @@ end_per_group(_, Config) ->
|
|||
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
|
||||
],
|
||||
Cmd = [InitSlapd, SlapdDir, {"~b", [TcpPort]}],
|
||||
case rabbit_ct_helpers:exec(Cmd) of
|
||||
{ok, Stdout} ->
|
||||
{match, [SlapdPid]} = re:run(
|
||||
|
@ -191,8 +174,7 @@ init_slapd(Config) ->
|
|||
[SlapdPid, TcpPort]),
|
||||
rabbit_ct_helpers:set_config(Config,
|
||||
[{slapd_pid, SlapdPid},
|
||||
{ldap_port, TcpPort},
|
||||
{ldap_tls_port, TlsPort}]);
|
||||
{ldap_port, TcpPort}]);
|
||||
_ ->
|
||||
_ = rabbit_ct_helpers:exec(["pkill", "-INT", "slapd"]),
|
||||
{skip, "Failed to initialize slapd(8)"}
|
||||
|
@ -224,10 +206,6 @@ end_internal(Config) ->
|
|||
ok = control_action(Config, delete_user, [?BOB_NAME]),
|
||||
ok = control_action(Config, delete_user, [?PETER_NAME]).
|
||||
|
||||
|
||||
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)
|
||||
when Testcase == ldap_and_internal;
|
||||
Testcase == internal_followed_ldap_and_internal ->
|
||||
|
@ -251,9 +229,6 @@ init_per_testcase(Testcase, Config)
|
|||
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)
|
||||
when Testcase == ldap_and_internal;
|
||||
Testcase == internal_followed_ldap_and_internal ->
|
||||
|
@ -295,434 +270,6 @@ end_per_testcase(Testcase, Config) ->
|
|||
%% Testsuite cases
|
||||
%% -------------------------------------------------------------------
|
||||
|
||||
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"),
|
||||
|
||||
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' => "",
|
||||
'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' => "",
|
||||
'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).
|
||||
|
||||
purge_connection(Config) ->
|
||||
{ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0,
|
||||
rabbit_auth_backend_ldap,
|
||||
|
|
|
@ -1,18 +1,13 @@
|
|||
#!/bin/sh
|
||||
# vim:sw=4:et:
|
||||
|
||||
set -eux
|
||||
set -ex
|
||||
|
||||
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 uri="ldap://localhost:$tcp_port"
|
||||
|
||||
readonly binddn="cn=config"
|
||||
readonly passwd=secret
|
||||
|
@ -73,10 +68,6 @@ loglevel 7
|
|||
database config
|
||||
rootdn "$binddn"
|
||||
rootpw $passwd
|
||||
|
||||
TLSCACertificateFile $cacertfile
|
||||
TLSCertificateFile $server_certfile
|
||||
TLSCertificateKeyFile $server_keyfile
|
||||
EOF
|
||||
|
||||
cat "$conf_file"
|
||||
|
@ -88,7 +79,7 @@ mkdir -p "$conf_dir"
|
|||
"$slapd" \
|
||||
-f "$conf_file" \
|
||||
-F "$conf_dir" \
|
||||
-h "$tcp_uri $tls_uri"
|
||||
-h "$uri"
|
||||
|
||||
readonly auth="-x -D $binddn -w $passwd"
|
||||
|
||||
|
@ -96,7 +87,7 @@ readonly auth="-x -D $binddn -w $passwd"
|
|||
# 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;
|
||||
ldapsearch $auth -H "$uri" -LLL -b cn=config dn && break;
|
||||
sleep 1
|
||||
done
|
||||
|
||||
|
@ -115,22 +106,22 @@ mkdir -p "$example_data_dir"
|
|||
# shellcheck disable=SC2086
|
||||
sed -E -e "s,^olcDbDirectory:.*,olcDbDirectory: $example_data_dir," \
|
||||
< "$example_ldif_dir/global.ldif" | \
|
||||
ldapadd $auth -H "$tcp_uri"
|
||||
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 "$tcp_uri"
|
||||
ldapadd $auth -H "$uri"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
ldapmodify $auth -H "$tcp_uri" -f "$example_ldif_dir/refint_1.ldif"
|
||||
ldapmodify $auth -H "$uri" -f "$example_ldif_dir/refint_1.ldif"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
ldapadd $auth -H "$tcp_uri" -f "$example_ldif_dir/refint_2.ldif"
|
||||
ldapadd $auth -H "$uri" -f "$example_ldif_dir/refint_2.ldif"
|
||||
|
||||
# shellcheck disable=SC2086
|
||||
ldapsearch $auth -H "$tcp_uri" -LLL -b cn=config dn
|
||||
ldapsearch $auth -H "$uri" -LLL -b cn=config dn
|
||||
|
||||
echo SLAPD_PID="$(cat "$pidfile")"
|
||||
|
|
|
@ -8,7 +8,6 @@
|
|||
-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}]).
|
||||
|
|
|
@ -18,9 +18,8 @@
|
|||
is_authorized_vhost_visible_for_monitoring/2,
|
||||
is_authorized_global_parameters/2]).
|
||||
-export([user/1]).
|
||||
-export([bad_request/3, service_unavailable/3, not_authorised/3, bad_request_exception/4,
|
||||
-export([bad_request/3, service_unavailable/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]).
|
||||
|
@ -676,12 +675,10 @@ 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).
|
||||
|
||||
|
|
Loading…
Reference in New Issue