Merge branch 'stable' into rabbitmq-auth-backend-ldap-15

This commit is contained in:
Daniil Fedotov 2016-04-20 11:04:55 +01:00
commit 8406c0cf77
8 changed files with 242 additions and 64 deletions

View File

@ -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

View File

@ -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

View File

@ -46,3 +46,42 @@ 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

View File

@ -0,0 +1,3 @@
dn: cn=module{1},cn=config
add: olcmoduleload
olcmoduleload: refint

View File

@ -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

View File

@ -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

View File

@ -163,23 +163,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 +193,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 +203,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 +246,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 +343,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},

View File

@ -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>>}).
%%--------------------------------------------------------------------
@ -51,16 +62,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,16 +86,19 @@ 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()},
@ -147,39 +164,45 @@ tag_attribution_test_() ->
%%--------------------------------------------------------------------
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} <- logins(),
{N, {EnvGood, Env}} <- login_envs()]).
%% Format for login tests, {Outcome, FilterList, Login}.
%% Tests skipped for each login_env reference in FilterList.
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}].
missing_credentials_for_authentication() ->
#amqp_params_network{username = <<"Alice">>,
password = <<"Alicja">>,
virtual_host = << ?VHOST >>}.
[{bad, [5], #amqp_params_network{}},
{bad, [5], #amqp_params_network{username = << ?ALICE_NAME >>}},
{bad, [5], #amqp_params_network{username = << ?ALICE_NAME >>,
password = <<"password">>}},
{bad, [5], #amqp_params_network{username = <<"Alice">>,
password = <<"Alicja">>,
virtual_host = << ?VHOST >>}},
{bad, [1, 2, 3, 4, 6], ?CAROL},
{good, [5], ?ALICE},
{good, [5], ?BOB},
{good, [1, 2, 3, 4, 6], ?PETER}].
%% 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, {bad, other_bind_broken_env()}}].
base_login_env() ->
[{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},
{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() ->
@ -197,13 +220,47 @@ 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).
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].