Merge branch 'stable'
This commit is contained in:
commit
231d9ecea2
|
|
@ -8,9 +8,18 @@ cn: wheel
|
|||
member: cn=Alice,ou=people,dc=example,dc=com
|
||||
member: cn=Charlie,ou=people,dc=example,dc=com
|
||||
member: cn=Dominic,ou=people,dc=example,dc=com
|
||||
member: uid=peter,ou=people,dc=example,dc=com
|
||||
|
||||
dn: cn=people,ou=groups,dc=example,dc=com
|
||||
objectclass: groupOfNames
|
||||
cn: people
|
||||
member: cn=Charlie,ou=people,dc=example,dc=com
|
||||
member: cn=Dominic,ou=people,dc=example,dc=com
|
||||
member: uid=peter,ou=people,dc=example,dc=com
|
||||
|
||||
dn: cn=staff,ou=groups,dc=example,dc=com
|
||||
objectclass: groupOfNames
|
||||
cn: people
|
||||
member: cn=Charlie,ou=people,dc=example,dc=com
|
||||
member: cn=Dominic,ou=people,dc=example,dc=com
|
||||
member: uid=peter,ou=people,dc=example,dc=com
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
dn: cn=module,cn=config
|
||||
cn: module
|
||||
objectClass: olcModuleList
|
||||
olcModuleLoad: memberof
|
||||
olcModulePath: /usr/lib/ldap
|
||||
|
||||
dn: olcOverlay={0}memberof,olcDatabase={1}hdb,cn=config
|
||||
objectClass: olcConfig
|
||||
objectClass: olcMemberOf
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: top
|
||||
olcOverlay: memberof
|
||||
olcMemberOfDangling: ignore
|
||||
olcMemberOfRefInt: TRUE
|
||||
olcMemberOfGroupOC: groupOfNames
|
||||
olcMemberOfMemberAD: member
|
||||
olcMemberOfMemberOfAD: memberOf
|
||||
|
|
@ -35,8 +35,53 @@ cn: Charlie
|
|||
sn: Charlie Boy
|
||||
userPassword: password
|
||||
|
||||
dn: cn=Edward,ou=people,dc=example,dc=com
|
||||
objectClass: person
|
||||
cn: Edward
|
||||
sn: Ed
|
||||
userPassword: password
|
||||
|
||||
dn: cn=John Doe,ou=people,dc=example,dc=com
|
||||
objectClass: person
|
||||
cn: John Doe
|
||||
sn: Doe
|
||||
userPassword: password
|
||||
|
||||
dn: uid=peter,ou=people,dc=example,dc=com
|
||||
cn: Peter
|
||||
givenName: Peter
|
||||
sn: Jones
|
||||
uid: peter
|
||||
uidNumber: 5000
|
||||
gidNumber: 10000
|
||||
homeDirectory: /home/peter
|
||||
mail: peter.jones@example.com
|
||||
objectClass: top
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
loginShell: /bin/bash
|
||||
userPassword: password
|
||||
memberOf: cn=wheel,ou=groups,dc=example,dc=com
|
||||
memberOf: cn=staff,ou=groups,dc=example,dc=com
|
||||
memberOf: cn=people,ou=groups,dc=example,dc=com
|
||||
|
||||
dn: uid=carol,ou=people,dc=example,dc=com
|
||||
cn: Carol
|
||||
givenName: Carol
|
||||
sn: Meyers
|
||||
uid: carol
|
||||
uidNumber: 655
|
||||
gidNumber: 10000
|
||||
homeDirectory: /home/carol
|
||||
mail: carol.meyers@example.com
|
||||
objectClass: top
|
||||
objectClass: posixAccount
|
||||
objectClass: shadowAccount
|
||||
objectClass: inetOrgPerson
|
||||
objectClass: organizationalPerson
|
||||
objectClass: person
|
||||
loginShell: /bin/bash
|
||||
userPassword: password
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
dn: cn=module{1},cn=config
|
||||
add: olcmoduleload
|
||||
olcmoduleload: refint
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
dn: olcOverlay={1}refint,olcDatabase={1}hdb,cn=config
|
||||
objectClass: olcConfig
|
||||
objectClass: olcOverlayConfig
|
||||
objectClass: olcRefintConfig
|
||||
objectClass: top
|
||||
olcOverlay: {1}refint
|
||||
olcRefintAttribute: memberof member manager owner
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
DIR=$(dirname $0)
|
||||
|
||||
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f ${DIR}/global.ldif
|
||||
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ${DIR}/memberof_init.ldif
|
||||
sudo ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f ${DIR}/refint_1.ldif
|
||||
sudo ldapadd -Q -Y EXTERNAL -H ldapi:/// -f ${DIR}/refint_2.ldif
|
||||
ldapadd -x -D cn=admin,dc=example,dc=com -w admin -f ${DIR}/people.ldif
|
||||
ldapadd -x -D cn=admin,dc=example,dc=com -w admin -f ${DIR}/groups.ldif
|
||||
ldapadd -x -D cn=admin,dc=example,dc=com -w admin -f ${DIR}/rabbit.ldif
|
||||
|
|
|
|||
|
|
@ -46,24 +46,27 @@ user_login_authentication(Username, []) ->
|
|||
[Username, log_result(R)]),
|
||||
R;
|
||||
|
||||
user_login_authentication(Username, [{password, <<>>}]) ->
|
||||
%% Password "" is special in LDAP, see
|
||||
%% https://tools.ietf.org/html/rfc4513#section-5.1.2
|
||||
?L("CHECK: unauthenticated login for ~s", [Username]),
|
||||
?L("DECISION: unauthenticated login for ~s: denied", [Username]),
|
||||
{refused, "user '~s' - unauthenticated bind not allowed", [Username]};
|
||||
|
||||
user_login_authentication(User, [{password, PW}]) ->
|
||||
?L("CHECK: login for ~s", [User]),
|
||||
R = case dn_lookup_when() of
|
||||
prebind -> UserDN = username_to_dn_prebind(User),
|
||||
with_ldap({ok, {UserDN, PW}},
|
||||
fun(L) -> do_login(User, UserDN, PW, L) end);
|
||||
_ -> with_ldap({ok, {fill_user_dn_pattern(User), PW}},
|
||||
fun(L) -> do_login(User, unknown, PW, L) end)
|
||||
end,
|
||||
?L("DECISION: login for ~s: ~p", [User, log_result(R)]),
|
||||
R;
|
||||
user_login_authentication(Username, AuthProps) when is_list(AuthProps) ->
|
||||
case pget(password, AuthProps) of
|
||||
undefined -> user_login_authentication(Username, []);
|
||||
<<>> ->
|
||||
%% Password "" is special in LDAP, see
|
||||
%% https://tools.ietf.org/html/rfc4513#section-5.1.2
|
||||
?L("CHECK: unauthenticated login for ~s", [Username]),
|
||||
?L("DECISION: unauthenticated login for ~s: denied", [Username]),
|
||||
{refused, "user '~s' - unauthenticated bind not allowed", [Username]};
|
||||
PW ->
|
||||
?L("CHECK: login for ~s", [Username]),
|
||||
R = case dn_lookup_when() of
|
||||
prebind -> UserDN = username_to_dn_prebind(Username),
|
||||
with_ldap({ok, {UserDN, PW}},
|
||||
login_fun(Username, UserDN, PW, AuthProps));
|
||||
_ -> with_ldap({ok, {fill_user_dn_pattern(Username), PW}},
|
||||
login_fun(Username, unknown, PW, AuthProps))
|
||||
end,
|
||||
?L("DECISION: login for ~s: ~p", [Username, log_result(R)]),
|
||||
R
|
||||
end;
|
||||
|
||||
user_login_authentication(Username, AuthProps) ->
|
||||
exit({unknown_auth_props, Username, AuthProps}).
|
||||
|
|
@ -163,23 +166,20 @@ evaluate0({'or', Queries}, Args, User, LDAP) when is_list(Queries) ->
|
|||
|
||||
evaluate0({equals, StringQuery1, StringQuery2}, Args, User, LDAP) ->
|
||||
safe_eval(fun (String1, String2) ->
|
||||
R = String1 =:= String2,
|
||||
R = if String1 =:= String2 -> true;
|
||||
true -> is_multi_attr_member(String1, String2)
|
||||
end,
|
||||
?L1("evaluated equals \"~s\", \"~s\": ~s",
|
||||
[String1, String2, R]),
|
||||
[format_multi_attr(String1),
|
||||
format_multi_attr(String2), R]),
|
||||
R
|
||||
end,
|
||||
evaluate(StringQuery1, Args, User, LDAP),
|
||||
evaluate(StringQuery2, Args, User, LDAP));
|
||||
|
||||
evaluate0({match, StringQuery, REQuery}, Args, User, LDAP) ->
|
||||
safe_eval(fun (String, RE) ->
|
||||
R = case re:run(String, RE) of
|
||||
{match, _} -> true;
|
||||
nomatch -> false
|
||||
end,
|
||||
?L1("evaluated match \"~s\" against RE \"~s\": ~s",
|
||||
[String, RE, R]),
|
||||
R
|
||||
safe_eval(fun (String1, String2) ->
|
||||
do_match(String1, String2)
|
||||
end,
|
||||
evaluate(StringQuery, Args, User, LDAP),
|
||||
evaluate(REQuery, Args, User, LDAP));
|
||||
|
|
@ -196,7 +196,7 @@ evaluate0({attribute, DNPattern, AttributeName}, Args, _User, LDAP) ->
|
|||
DN = fill(DNPattern, Args),
|
||||
R = attribute(DN, AttributeName, LDAP),
|
||||
?L1("evaluated attribute \"~s\" for \"~s\": ~p",
|
||||
[AttributeName, DN, R]),
|
||||
[AttributeName, DN, format_multi_attr(R)]),
|
||||
R;
|
||||
|
||||
evaluate0(Q, Args, _User, _LDAP) ->
|
||||
|
|
@ -206,6 +206,32 @@ safe_eval(_F, {error, _}, _) -> false;
|
|||
safe_eval(_F, _, {error, _}) -> false;
|
||||
safe_eval(F, V1, V2) -> F(V1, V2).
|
||||
|
||||
do_match(S1, S2) ->
|
||||
case re:run(S1, S2) of
|
||||
{match, _} -> log_match(S1, S2, R = true),
|
||||
R;
|
||||
nomatch ->
|
||||
%% Do match bidirectionally, if intial RE consists of
|
||||
%% multi attributes, else log match and return result.
|
||||
case S2 of
|
||||
S when length(S) > 1 ->
|
||||
R = case re:run(S2, S1) of
|
||||
{match, _} -> true;
|
||||
nomatch -> false
|
||||
end,
|
||||
log_match(S2, S1, R),
|
||||
R;
|
||||
_ ->
|
||||
log_match(S1, S2, R = false),
|
||||
R
|
||||
end
|
||||
end.
|
||||
|
||||
log_match(String, RE, Result) ->
|
||||
?L1("evaluated match \"~s\" against RE \"~s\": ~s",
|
||||
[format_multi_attr(String),
|
||||
format_multi_attr(RE), Result]).
|
||||
|
||||
object_exists(DN, Filter, LDAP) ->
|
||||
case eldap:search(LDAP,
|
||||
[{base, DN},
|
||||
|
|
@ -223,11 +249,8 @@ attribute(DN, AttributeName, LDAP) ->
|
|||
[{base, DN},
|
||||
{filter, eldap:present("objectClass")},
|
||||
{attributes, [AttributeName]}]) of
|
||||
{ok, #eldap_search_result{entries = [#eldap_entry{attributes = A}]}} ->
|
||||
case pget(AttributeName, A) of
|
||||
[Attr] -> Attr;
|
||||
_ -> {error, not_found}
|
||||
end;
|
||||
{ok, #eldap_search_result{entries = E = [#eldap_entry{}|_]}} ->
|
||||
get_attributes(AttributeName, E);
|
||||
{ok, #eldap_search_result{entries = _}} ->
|
||||
{error, not_found};
|
||||
{error, _} = E ->
|
||||
|
|
@ -323,6 +346,29 @@ get_or_create_conn(IsAnon, Servers, Opts) ->
|
|||
end
|
||||
end.
|
||||
|
||||
%% Get attribute(s) from eldap entry
|
||||
get_attributes(_AttrName, []) -> {error, not_found};
|
||||
get_attributes(AttrName, [#eldap_entry{attributes = A}|Rem]) ->
|
||||
case pget(AttrName, A) of
|
||||
[Attr|[]] -> Attr;
|
||||
Attrs when length(Attrs) > 1 -> Attrs;
|
||||
_ -> get_attributes(AttrName, Rem)
|
||||
end;
|
||||
get_attributes(AttrName, [_|Rem]) -> get_attributes(AttrName, Rem).
|
||||
|
||||
%% Format multiple attribute values for logging
|
||||
format_multi_attr(Attrs) ->
|
||||
format_multi_attr(io_lib:printable_list(Attrs), Attrs).
|
||||
|
||||
format_multi_attr(true, Attrs) -> Attrs;
|
||||
format_multi_attr(_, Attrs) when is_list(Attrs) -> string:join(Attrs, "; ");
|
||||
format_multi_attr(_, Error) -> Error.
|
||||
|
||||
|
||||
%% In case of multiple attributes, check for equality bi-directionally
|
||||
is_multi_attr_member(Str1, Str2) ->
|
||||
lists:member(Str1, Str2) orelse lists:member(Str2, Str1).
|
||||
|
||||
purge_conn(IsAnon, Servers, Opts) ->
|
||||
Conns = get(ldap_conns),
|
||||
Key = {IsAnon, Servers, Opts},
|
||||
|
|
@ -372,7 +418,17 @@ env(F) ->
|
|||
{ok, V} = application:get_env(rabbitmq_auth_backend_ldap, F),
|
||||
V.
|
||||
|
||||
login_fun(User, UserDN, Password, AuthProps) ->
|
||||
fun(L) -> case pget(vhost, AuthProps) of
|
||||
undefined -> do_login(User, UserDN, Password, L);
|
||||
VHost -> do_login(User, UserDN, Password, VHost, L)
|
||||
end
|
||||
end.
|
||||
|
||||
do_login(Username, PrebindUserDN, Password, LDAP) ->
|
||||
do_login(Username, PrebindUserDN, Password, <<>>, LDAP).
|
||||
|
||||
do_login(Username, PrebindUserDN, Password, VHost, LDAP) ->
|
||||
UserDN = case PrebindUserDN of
|
||||
unknown -> username_to_dn(Username, LDAP, dn_lookup_when());
|
||||
_ -> PrebindUserDN
|
||||
|
|
@ -380,30 +436,31 @@ do_login(Username, PrebindUserDN, Password, LDAP) ->
|
|||
User = #auth_user{username = Username,
|
||||
impl = #impl{user_dn = UserDN,
|
||||
password = Password}},
|
||||
DTQ = fun (LDAPn) -> do_tag_queries(Username, UserDN, User, LDAPn) end,
|
||||
DTQ = fun (LDAPn) -> do_tag_queries(Username, UserDN, User, VHost, LDAPn) end,
|
||||
TagRes = case env(other_bind) of
|
||||
as_user -> DTQ(LDAP);
|
||||
_ -> with_ldap(creds(User), DTQ)
|
||||
end,
|
||||
case TagRes of
|
||||
{ok, L} -> case [E || {_, E = {error, _}} <- L] of
|
||||
[] -> Tags = [Tag || {Tag, true} <- L],
|
||||
{ok, User#auth_user{tags = Tags}};
|
||||
[E | _] -> E
|
||||
end;
|
||||
{ok, L} -> {ok, User#auth_user{tags = [Tag || {Tag, true} <- L]}};
|
||||
E -> E
|
||||
end.
|
||||
|
||||
do_tag_queries(Username, UserDN, User, LDAP) ->
|
||||
do_tag_queries(Username, UserDN, User, VHost, LDAP) ->
|
||||
{ok, [begin
|
||||
?L1("CHECK: does ~s have tag ~s?", [Username, Tag]),
|
||||
R = evaluate(Q, [{username, Username},
|
||||
{user_dn, UserDN}], User, LDAP),
|
||||
{user_dn, UserDN} | vhost_if_defined(VHost)],
|
||||
User, LDAP),
|
||||
?L1("DECISION: does ~s have tag ~s? ~p",
|
||||
[Username, Tag, R]),
|
||||
{Tag, R}
|
||||
end || {Tag, Q} <- env(tag_queries)]}.
|
||||
|
||||
vhost_if_defined([]) -> [];
|
||||
vhost_if_defined(<<>>) -> [];
|
||||
vhost_if_defined(VHost) -> [{vhost, VHost}].
|
||||
|
||||
dn_lookup_when() -> case {env(dn_lookup_attribute), env(dn_lookup_bind)} of
|
||||
{none, _} -> never;
|
||||
{_, as_user} -> postbind;
|
||||
|
|
|
|||
|
|
@ -21,15 +21,26 @@
|
|||
|
||||
-define(ALICE_NAME, "Alice").
|
||||
-define(BOB_NAME, "Bob").
|
||||
-define(CAROL_NAME, "Carol").
|
||||
-define(PETER_NAME, "Peter").
|
||||
|
||||
-define(VHOST, "test").
|
||||
|
||||
-define(ALICE, #amqp_params_network{username = << ?ALICE_NAME >>,
|
||||
-define(ALICE, #amqp_params_network{username = <<?ALICE_NAME>>,
|
||||
password = <<"password">>,
|
||||
virtual_host = << ?VHOST >>}).
|
||||
virtual_host = <<?VHOST>>}).
|
||||
|
||||
-define(BOB, #amqp_params_network{username = << ?BOB_NAME >>,
|
||||
-define(BOB, #amqp_params_network{username = <<?BOB_NAME>>,
|
||||
password = <<"password">>,
|
||||
virtual_host = <<?VHOST>>}).
|
||||
|
||||
-define(CAROL, #amqp_params_network{username = <<?CAROL_NAME>>,
|
||||
password = <<"password">>,
|
||||
virtual_host = << ?VHOST >>}).
|
||||
virtual_host = <<?VHOST>>}).
|
||||
|
||||
-define(PETER, #amqp_params_network{username = <<?PETER_NAME>>,
|
||||
password = <<"password">>,
|
||||
virtual_host = <<?VHOST>>}).
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
|
|
@ -43,7 +54,8 @@ ldap_only_test_() ->
|
|||
{"LDAP Constant", const()},
|
||||
{"LDAP String match", string_match()},
|
||||
{"LDAP Boolean check", boolean_logic()},
|
||||
{"LDAP Tags", tag_check([monitor])}
|
||||
{"LDAP Tags", tag_check([monitor])},
|
||||
{"LDAP Tag Substitution", tag_check_subst()}
|
||||
]}.
|
||||
|
||||
ldap_and_internal_test_() ->
|
||||
|
|
@ -51,16 +63,19 @@ ldap_and_internal_test_() ->
|
|||
fun () ->
|
||||
ok = application:set_env(rabbit, auth_backends,
|
||||
[{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]),
|
||||
ok = control_action(add_user, [ ?ALICE_NAME, ""]),
|
||||
ok = control_action(set_permissions, [ ?ALICE_NAME, "prefix-.*", "prefix-.*", "prefix-.*"]),
|
||||
ok = control_action(set_user_tags, [ ?ALICE_NAME, "management", "foo"]),
|
||||
ok = control_action(add_user, [ ?BOB_NAME, ""]),
|
||||
ok = control_action(set_permissions, [ ?BOB_NAME, "", "", ""])
|
||||
ok = control_action(add_user, [?ALICE_NAME, ""]),
|
||||
ok = control_action(set_permissions, [?ALICE_NAME, "prefix-.*", "prefix-.*", "prefix-.*"]),
|
||||
ok = control_action(set_user_tags, [?ALICE_NAME, "management", "foo"]),
|
||||
ok = control_action(add_user, [?BOB_NAME, ""]),
|
||||
ok = control_action(set_permissions, [?BOB_NAME, "", "", ""]),
|
||||
ok = control_action(add_user, [?PETER_NAME, ""]),
|
||||
ok = control_action(set_permissions, [?PETER_NAME, "", "", ""])
|
||||
end,
|
||||
fun (_) ->
|
||||
ok = application:unset_env(rabbit, auth_backends),
|
||||
ok = control_action(delete_user, [ ?ALICE_NAME ]),
|
||||
ok = control_action(delete_user, [ ?BOB_NAME ])
|
||||
ok = control_action(delete_user, [?ALICE_NAME]),
|
||||
ok = control_action(delete_user, [?BOB_NAME]),
|
||||
ok = control_action(delete_user, [?PETER_NAME])
|
||||
end,
|
||||
[ {"LDAP&Internal Login", login()},
|
||||
{"LDAP&Internal Permissions", permission_match()},
|
||||
|
|
@ -72,63 +87,142 @@ internal_followed_ldap_and_internal_test_() ->
|
|||
fun () ->
|
||||
ok = application:set_env(rabbit, auth_backends,
|
||||
[rabbit_auth_backend_internal, {rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]),
|
||||
ok = control_action(add_user, [ ?ALICE_NAME, ""]),
|
||||
ok = control_action(set_permissions, [ ?ALICE_NAME, "prefix-.*", "prefix-.*", "prefix-.*"]),
|
||||
ok = control_action(set_user_tags, [ ?ALICE_NAME, "management", "foo"]),
|
||||
ok = control_action(add_user, [ ?BOB_NAME, ""]),
|
||||
ok = control_action(set_permissions, [ ?BOB_NAME, "", "", ""])
|
||||
ok = control_action(add_user, [?ALICE_NAME, ""]),
|
||||
ok = control_action(set_permissions, [?ALICE_NAME, "prefix-.*", "prefix-.*", "prefix-.*"]),
|
||||
ok = control_action(set_user_tags, [?ALICE_NAME, "management", "foo"]),
|
||||
ok = control_action(add_user, [?BOB_NAME, ""]),
|
||||
ok = control_action(set_permissions, [?BOB_NAME, "", "", ""]),
|
||||
ok = control_action(add_user, [?PETER_NAME, ""]),
|
||||
ok = control_action(set_permissions, [?PETER_NAME, "", "", ""])
|
||||
end,
|
||||
fun (_) ->
|
||||
ok = application:unset_env(rabbit, auth_backends),
|
||||
ok = control_action(delete_user, [ ?ALICE_NAME ]),
|
||||
ok = control_action(delete_user, [ ?BOB_NAME ])
|
||||
ok = control_action(delete_user, [?ALICE_NAME]),
|
||||
ok = control_action(delete_user, [?BOB_NAME]),
|
||||
ok = control_action(delete_user, [?PETER_NAME])
|
||||
end,
|
||||
[ {"Internal, LDAP&Internal Login", login()},
|
||||
{"Internal, LDAP&Internal Permissions", permission_match()},
|
||||
{"Internal, LDAP&Internal Tags", tag_check([monitor, management, foo])}
|
||||
]}.
|
||||
|
||||
tag_attribution_test_() ->
|
||||
{setup,
|
||||
fun () ->
|
||||
Cfg = case application:get_env(rabbit_auth_backend_ldap, tag_queries) of
|
||||
undefined -> undefined;
|
||||
{ok, X} -> X
|
||||
end,
|
||||
set_env(tag_query_configuration()),
|
||||
Cfg
|
||||
end,
|
||||
fun (undefined) ->
|
||||
ok;
|
||||
(Cfg) ->
|
||||
ok = application:set_env(rabbit_auth_backend_ldap, tag_queries, Cfg)
|
||||
end,
|
||||
{foreachx,
|
||||
fun (ldap_only) ->
|
||||
ok = application:set_env(rabbit, auth_backends, [rabbit_auth_backend_ldap]);
|
||||
(ldap_and_internal) ->
|
||||
ok = application:set_env(rabbit, auth_backends,
|
||||
[{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]),
|
||||
internal_authorization_setup();
|
||||
(internal_followed_by_ldap_and_internal) ->
|
||||
ok = application:set_env(rabbit, auth_backends,
|
||||
[rabbit_auth_backend_internal,
|
||||
{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]),
|
||||
internal_authorization_setup()
|
||||
end,
|
||||
fun (ldap_only, _) ->
|
||||
ok = application:unset_env(rabbit, auth_backends);
|
||||
(BackendCfg, _) when BackendCfg == ldap_and_internal;
|
||||
BackendCfg == internal_followed_by_ldap_and_internal ->
|
||||
internal_authorization_teardown(),
|
||||
ok = application:unset_env(rabbit, auth_backends)
|
||||
end,
|
||||
[{ldap_only,
|
||||
fun(_, _) ->
|
||||
{"LDAP Tag attribution",
|
||||
tag_check(<<"Edward">>, <<"password">>, [monitor, normal])}
|
||||
end},
|
||||
{ldap_and_internal,
|
||||
fun(_, _) ->
|
||||
{"LDAP & Internal Tag attribution",
|
||||
tag_check(<<"Edward">>, <<"password">>,
|
||||
[monitor, normal] ++ internal_authorization_tags())}
|
||||
end},
|
||||
{internal_followed_by_ldap_and_internal,
|
||||
fun(_, _) ->
|
||||
{"Internal followed by LDAP & Internal Tag attribution",
|
||||
tag_check(<<"Edward">>, <<"password">>,
|
||||
[monitor, normal] ++ internal_authorization_tags())}
|
||||
end}
|
||||
]
|
||||
}
|
||||
}.
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
login() ->
|
||||
[test_login(Env, L, case {LGood, EnvGood} of
|
||||
{good, good} -> fun succ/1;
|
||||
_ -> fun fail/1
|
||||
end) || {LGood, L} <- logins(),
|
||||
{EnvGood, Env} <- login_envs()].
|
||||
lists:flatten(
|
||||
[test_login({N, Env}, L, FilterList, case {LGood, EnvGood} of
|
||||
{good, good} -> fun succ/1;
|
||||
_ -> fun fail/1
|
||||
end) ||
|
||||
{LGood, FilterList, L, _Tags} <- logins(),
|
||||
{N, {EnvGood, Env}} <- login_envs()]).
|
||||
|
||||
logins() ->
|
||||
[{bad, #amqp_params_network{}},
|
||||
{bad, #amqp_params_network{username = << ?ALICE_NAME >>}},
|
||||
{bad, #amqp_params_network{username = << ?ALICE_NAME >>,
|
||||
password = <<"password">>}},
|
||||
{bad, missing_credentials_for_authentication()},
|
||||
{good, ?ALICE},
|
||||
{good, ?BOB}].
|
||||
logins() -> logins_network() ++ logins_direct().
|
||||
|
||||
missing_credentials_for_authentication() ->
|
||||
#amqp_params_network{username = <<"Alice">>,
|
||||
password = <<"Alicja">>,
|
||||
virtual_host = << ?VHOST >>}.
|
||||
%% Format for login tests, {Outcome, FilterList, Login, Tags}.
|
||||
%% Tests skipped for each login_env reference in FilterList.
|
||||
logins_network() ->
|
||||
[{bad, [5, 6], #amqp_params_network{}, []},
|
||||
{bad, [5, 6], #amqp_params_network{username = <<?ALICE_NAME>>}, []},
|
||||
{bad, [5, 6], #amqp_params_network{username = <<?ALICE_NAME>>,
|
||||
password = <<"password">>}, []},
|
||||
{bad, [5, 6], #amqp_params_network{username = <<"Alice">>,
|
||||
password = <<"Alicja">>,
|
||||
virtual_host = <<?VHOST>>}, []},
|
||||
{bad, [1, 2, 3, 4, 6, 7], ?CAROL, []},
|
||||
{good, [5, 6], ?ALICE, []},
|
||||
{good, [5, 6], ?BOB, []},
|
||||
{good, [1, 2, 3, 4, 6, 7], ?PETER, []}].
|
||||
|
||||
logins_direct() ->
|
||||
[{bad, [5], #amqp_params_direct{}, []},
|
||||
{bad, [5], #amqp_params_direct{username = <<?ALICE_NAME>>}, []},
|
||||
{bad, [5], #amqp_params_direct{username = <<?ALICE_NAME>>,
|
||||
password = <<"password">>}, [management]},
|
||||
{good, [5], #amqp_params_direct{username = <<?ALICE_NAME>>,
|
||||
password = <<"password">>,
|
||||
virtual_host = <<?VHOST>>}, [management]}].
|
||||
|
||||
%% Format for login envs, {Reference, {Outcome, Env}}
|
||||
login_envs() ->
|
||||
[{good, base_login_env()},
|
||||
{good, dn_lookup_pre_bind_env()},
|
||||
{good, other_bind_admin_env()},
|
||||
{good, other_bind_anon_env()},
|
||||
{bad, other_bind_broken_env()}].
|
||||
[{1, {good, base_login_env()}},
|
||||
{2, {good, dn_lookup_pre_bind_env()}},
|
||||
{3, {good, other_bind_admin_env()}},
|
||||
{4, {good, other_bind_anon_env()}},
|
||||
{5, {good, posix_vhost_access_multiattr_env()}},
|
||||
{6, {good, tag_queries_subst_env()}},
|
||||
{7, {bad, other_bind_broken_env()}}].
|
||||
|
||||
base_login_env() ->
|
||||
[{user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
|
||||
[{user_dn_pattern, "cn=${username},ou=People,dc=example,dc=com"},
|
||||
{dn_lookup_attribute, none},
|
||||
{dn_lookup_base, none},
|
||||
{dn_lookup_bind, as_user},
|
||||
{other_bind, as_user}].
|
||||
{other_bind, as_user},
|
||||
{tag_queries, [{monitor, {constant, true}},
|
||||
{administrator, {constant, false}},
|
||||
{management, {constant, false}}]},
|
||||
{vhost_access_query, {exists, "ou=${vhost},ou=vhosts,dc=example,dc=com"}}].
|
||||
|
||||
%% TODO configure OpenLDAP to allow a dn_lookup_post_bind_env()
|
||||
dn_lookup_pre_bind_env() ->
|
||||
[{user_dn_pattern, "${username}"},
|
||||
[{user_dn_pattern, "${username}"},
|
||||
{dn_lookup_attribute, "cn"},
|
||||
{dn_lookup_base, "OU=People,DC=example,DC=com"},
|
||||
{dn_lookup_bind, {"cn=admin,dc=example,dc=com", "admin"}}].
|
||||
|
|
@ -142,13 +236,52 @@ other_bind_anon_env() ->
|
|||
other_bind_broken_env() ->
|
||||
[{other_bind, {"cn=admin,dc=example,dc=com", "admi"}}].
|
||||
|
||||
test_login(Env, Login, ResultFun) ->
|
||||
?_test(try
|
||||
set_env(Env),
|
||||
ResultFun(Login)
|
||||
after
|
||||
set_env(base_login_env())
|
||||
end).
|
||||
tag_queries_subst_env() ->
|
||||
[{tag_queries, [{administrator, {constant, false}},
|
||||
{management,
|
||||
{exists, "ou=${vhost},ou=vhosts,dc=example,dc=com"}}]}].
|
||||
|
||||
posix_vhost_access_multiattr_env() ->
|
||||
[{user_dn_pattern, "uid=${username},ou=People,dc=example,dc=com"},
|
||||
{vhost_access_query,
|
||||
{'and', [{exists, "ou=${vhost},ou=vhosts,dc=example,dc=com"},
|
||||
{equals,
|
||||
{attribute, "${user_dn}","memberOf"},
|
||||
{string, "cn=wheel,ou=groups,dc=example,dc=com"}},
|
||||
{equals,
|
||||
{attribute, "${user_dn}","memberOf"},
|
||||
{string, "cn=people,ou=groups,dc=example,dc=com"}},
|
||||
{equals,
|
||||
{string, "cn=wheel,ou=groups,dc=example,dc=com"},
|
||||
{attribute,"${user_dn}","memberOf"}},
|
||||
{equals,
|
||||
{string, "cn=people,ou=groups,dc=example,dc=com"},
|
||||
{attribute, "${user_dn}","memberOf"}},
|
||||
{match,
|
||||
{attribute, "${user_dn}","memberOf"},
|
||||
{string, "cn=wheel,ou=groups,dc=example,dc=com"}},
|
||||
{match,
|
||||
{attribute, "${user_dn}","memberOf"},
|
||||
{string, "cn=people,ou=groups,dc=example,dc=com"}},
|
||||
{match,
|
||||
{string, "cn=wheel,ou=groups,dc=example,dc=com"},
|
||||
{attribute, "${user_dn}","memberOf"}},
|
||||
{match,
|
||||
{string, "cn=people,ou=groups,dc=example,dc=com"},
|
||||
{attribute, "${user_dn}","memberOf"}}
|
||||
]}}].
|
||||
|
||||
test_login({N, Env}, Login, FilterList, ResultFun) ->
|
||||
case lists:member(N, FilterList) of
|
||||
true -> [];
|
||||
_ ->
|
||||
?_test(try
|
||||
set_env(Env),
|
||||
ResultFun(Login)
|
||||
after
|
||||
set_env(base_login_env())
|
||||
end)
|
||||
end.
|
||||
|
||||
set_env(Env) ->
|
||||
[application:set_env(rabbitmq_auth_backend_ldap, K, V) || {K, V} <- Env].
|
||||
|
|
@ -161,7 +294,7 @@ fail(Login) -> ?assertMatch({error, _}, amqp_connection:start(Login)).
|
|||
in_group() ->
|
||||
X = [#'exchange.declare'{exchange = <<"test">>}],
|
||||
test_resource_funs([{?ALICE, X, ok},
|
||||
{?BOB, X, fail}]).
|
||||
{?BOB, X, fail}]).
|
||||
|
||||
const() ->
|
||||
Q = [#'queue.declare'{queue = <<"test">>}],
|
||||
|
|
@ -194,17 +327,70 @@ permission_match() ->
|
|||
#'queue.declare'{queue = <<"prefix-test">>},
|
||||
#'queue.bind'{exchange = N, queue = <<"prefix-test">>}]
|
||||
end,
|
||||
test_resource_funs([{?ALICE, B(<<"prefix-abc123">>), ok},
|
||||
{?ALICE, B(<<"abc123">>), fail},
|
||||
test_resource_funs([{?ALICE, B(<<"prefix-abc123">>), ok},
|
||||
{?ALICE, B(<<"abc123">>), fail},
|
||||
{?ALICE, B(<<"xch-Alice-abc123">>), fail}]).
|
||||
|
||||
tag_check(Tags) ->
|
||||
fun() ->
|
||||
{ok, User} = rabbit_access_control:check_user_pass_login(
|
||||
<< ?ALICE_NAME >>, <<"password">>),
|
||||
?assertEqual(Tags, User#user.tags)
|
||||
end.
|
||||
%% Tag check tests, with substitution
|
||||
tag_check_subst() ->
|
||||
lists:flatten(
|
||||
[test_tag_check(tag_check(Username, Password, VHost, Outcome, Tags)) ||
|
||||
{Outcome, _FilterList, #amqp_params_direct{username = Username,
|
||||
password = Password,
|
||||
virtual_host = VHost},
|
||||
Tags} <- logins_direct()]).
|
||||
|
||||
%% Tag check
|
||||
tag_check(Tags) ->
|
||||
tag_check(<<?ALICE_NAME>>, <<"password">>, Tags).
|
||||
|
||||
tag_check(Username, Password, Tags) ->
|
||||
tag_check(Username, Password, <<>>, good, Tags).
|
||||
|
||||
tag_check(Username, Password, VHost, Outcome, Tags)
|
||||
when is_binary(Username), is_binary(Password), is_binary(VHost), is_list(Tags) ->
|
||||
fun() ->
|
||||
{ok, User} = rabbit_access_control:check_user_login(
|
||||
Username, [{password, Password}, {vhost, VHost}]),
|
||||
tag_check_outcome(Outcome, Tags, User)
|
||||
end;
|
||||
tag_check(_, _, _, _, _) -> fun() -> [] end.
|
||||
|
||||
tag_check_outcome(good, Tags, User) -> ?assertEqual(Tags, User#user.tags);
|
||||
tag_check_outcome(bad, Tags, User) -> ?assertNotEqual(Tags, User#user.tags).
|
||||
|
||||
test_tag_check(TagCheckFun) ->
|
||||
?_test(try
|
||||
set_env(tag_queries_subst_env()),
|
||||
TagCheckFun()
|
||||
after
|
||||
set_env(base_login_env())
|
||||
end).
|
||||
|
||||
|
||||
tag_query_configuration() ->
|
||||
[{tag_queries,
|
||||
[{administrator, {constant, false}},
|
||||
%% Query result for tag `management` is FALSE
|
||||
%% because this object does NOT exist.
|
||||
{management,
|
||||
{exists, "cn=${username},ou=Faculty,dc=Computer Science,dc=Engineering"}},
|
||||
{monitor, {constant, true}},
|
||||
%% Query result for tag `normal` is TRUE because
|
||||
%% this object exists.
|
||||
{normal,
|
||||
{exists, "cn=${username},ou=people,dc=example,dc=com"}}]}].
|
||||
|
||||
internal_authorization_setup() ->
|
||||
ok = control_action(add_user, ["Edward", ""]),
|
||||
ok = control_action(set_user_tags, ["Edward"] ++
|
||||
[ atom_to_list(T) || T <- internal_authorization_tags() ]).
|
||||
|
||||
internal_authorization_teardown() ->
|
||||
ok = control_action(delete_user, ["Edward"]).
|
||||
|
||||
internal_authorization_tags() ->
|
||||
[foo, bar].
|
||||
|
||||
%%--------------------------------------------------------------------
|
||||
|
||||
|
|
@ -249,7 +435,7 @@ default_options() -> [{"-p", ?VHOST}, {"-q", "false"}].
|
|||
expand_options(As, Bs) ->
|
||||
lists:foldl(fun({K, _}=A, R) ->
|
||||
case proplists:is_defined(K, R) of
|
||||
true -> R;
|
||||
true -> R;
|
||||
false -> [A | R]
|
||||
end
|
||||
end, Bs, As).
|
||||
|
|
|
|||
Loading…
Reference in New Issue