diff options
Diffstat (limited to 'deps/rabbitmq_auth_backend_ldap/test')
9 files changed, 1629 insertions, 0 deletions
diff --git a/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE.erl b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE.erl new file mode 100644 index 0000000000..1bc6136178 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE.erl @@ -0,0 +1,55 @@ +%% 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) 2016-2020 VMware, Inc. or its affiliates. All rights reserved. +%% + +-module(config_schema_SUITE). + +-compile(export_all). + +all() -> + [ + run_snippets + ]. + +%% ------------------------------------------------------------------- +%% Testsuite setup/teardown. +%% ------------------------------------------------------------------- + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + Config1 = rabbit_ct_helpers:run_setup_steps(Config), + rabbit_ct_config_schema:init_schemas(rabbitmq_auth_backend_ldap, Config1). + + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config). + +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase), + Config1 = rabbit_ct_helpers:set_config(Config, [ + {rmq_nodename_suffix, Testcase} + ]), + rabbit_ct_helpers:run_steps(Config1, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()). + +end_per_testcase(Testcase, Config) -> + Config1 = rabbit_ct_helpers:run_steps(Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()), + rabbit_ct_helpers:testcase_finished(Config1, Testcase). + +%% ------------------------------------------------------------------- +%% Testcases. +%% ------------------------------------------------------------------- + +run_snippets(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + ?MODULE, run_snippets1, [Config]). + +run_snippets1(Config) -> + rabbit_ct_config_schema:run_snippets(Config). + diff --git a/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/cacert.pem b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/cacert.pem new file mode 100644 index 0000000000..eaf6b67806 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/cacert.pem @@ -0,0 +1 @@ +I'm not a certificate diff --git a/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/cert.pem b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/cert.pem new file mode 100644 index 0000000000..eaf6b67806 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/cert.pem @@ -0,0 +1 @@ +I'm not a certificate diff --git a/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/key.pem b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/key.pem new file mode 100644 index 0000000000..eaf6b67806 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/certs/key.pem @@ -0,0 +1 @@ +I'm not a certificate diff --git a/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/rabbitmq_auth_backend_ldap.snippets b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/rabbitmq_auth_backend_ldap.snippets new file mode 100644 index 0000000000..7e4ba70cec --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/config_schema_SUITE_data/rabbitmq_auth_backend_ldap.snippets @@ -0,0 +1,276 @@ +[{ldap_servers, + "auth_ldap.servers.1 = DC1.domain.com + auth_ldap.servers.2 = DC1.eng.domain.com", + [{rabbitmq_auth_backend_ldap, + [{servers,["DC1.domain.com","DC1.eng.domain.com"]}]}], + [rabbitmq_auth_backend_ldap]}, + {ldap_servers_short, + "auth_ldap.servers.1 = hostname1 + auth_ldap.servers.2 = hostname2", + [{rabbitmq_auth_backend_ldap,[{servers,["hostname1","hostname2"]}]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_port, + "auth_ldap.port = 1234", + [{rabbitmq_auth_backend_ldap,[ + {port, 1234} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_connection_pool_size, + "auth_ldap.connection_pool_size = 128", + [{rabbitmq_auth_backend_ldap,[ + {pool_size, 128} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_servers_short, + "auth_ldap.servers.1 = hostname1 + auth_ldap.servers.2 = hostname2", + [{rabbitmq_auth_backend_ldap,[{servers,["hostname1","hostname2"]}]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_timeouts, + "auth_ldap.timeout = 50000 + auth_ldap.idle_timeout = 90000", + [{rabbitmq_auth_backend_ldap,[ + {timeout, 50000}, + {idle_timeout, 90000} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_use_ssl_true, + "auth_ldap.use_ssl = true", + [{rabbitmq_auth_backend_ldap,[ + {use_ssl, true} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_use_ssl_false, + "auth_ldap.use_ssl = false", + [{rabbitmq_auth_backend_ldap,[ + {use_ssl, false} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_use_starttls_true, + "auth_ldap.use_starttls = true", + [{rabbitmq_auth_backend_ldap,[ + {use_starttls, true} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_log_false, + "auth_ldap.log = false", + [{rabbitmq_auth_backend_ldap,[ + {log, false} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_log_true, + "auth_ldap.log = true", + [{rabbitmq_auth_backend_ldap,[ + {log, true} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_log_network, + "auth_ldap.log = network", + [{rabbitmq_auth_backend_ldap,[ + {log, network} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ldap_log_network_unsafe, + "auth_ldap.log = network_unsafe", + [{rabbitmq_auth_backend_ldap,[ + {log, network_unsafe} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {user_dn_pattern, + "auth_ldap.user_dn_pattern = ${ad_user}-${ad_domain}", + [{rabbitmq_auth_backend_ldap, + [{user_dn_pattern, "${ad_user}-${ad_domain}"}]}], + [rabbitmq_auth_backend_ldap]}, + + {user_bind_pattern, + "auth_ldap.user_bind_pattern = ${ad_user}-${ad_domain}", + [{rabbitmq_auth_backend_ldap, + [{user_bind_pattern, "${ad_user}-${ad_domain}"}]}], + [rabbitmq_auth_backend_ldap]}, + + {group_lookup_base, + "auth_ldap.group_lookup_base = DC=gopivotal,DC=com", + [{rabbitmq_auth_backend_ldap, + [{group_lookup_base, "DC=gopivotal,DC=com"}]}], + [rabbitmq_auth_backend_ldap]}, + + {dn_lookup, + "auth_ldap.dn_lookup_attribute = userPrincipalName + auth_ldap.dn_lookup_base = DC=gopivotal,DC=com + auth_ldap.dn_lookup_bind = as_user", + [{rabbitmq_auth_backend_ldap, + [{dn_lookup_attribute,"userPrincipalName"}, + {dn_lookup_base,"DC=gopivotal,DC=com"}, + {dn_lookup_bind,as_user}]}], + [rabbitmq_auth_backend_ldap]}, + + {db_lookup_bind, + "auth_ldap.dn_lookup_bind.user_dn = username + auth_ldap.dn_lookup_bind.password = password", + [{rabbitmq_auth_backend_ldap,[{dn_lookup_bind,{"username","password"}}]}], + [rabbitmq_auth_backend_ldap]}, + + {db_lookup_bind_anon, + "auth_ldap.dn_lookup_bind = anon", + [{rabbitmq_auth_backend_ldap,[{dn_lookup_bind,anon}]}], + [rabbitmq_auth_backend_ldap]}, + + {other_bind_anon, + "auth_ldap.other_bind = anon", + [{rabbitmq_auth_backend_ldap,[{other_bind,anon}]}], + [rabbitmq_auth_backend_ldap]}, + + {both_binds_anon, + "auth_ldap.dn_lookup_bind = anon + auth_ldap.other_bind = anon", + [{rabbitmq_auth_backend_ldap,[{dn_lookup_bind,anon}, + {other_bind,anon}]}], + [rabbitmq_auth_backend_ldap]}, + + {other_bind_as_user, + "auth_ldap.other_bind = as_user", + [{rabbitmq_auth_backend_ldap,[{other_bind,as_user}]}], + [rabbitmq_auth_backend_ldap]}, + + {other_bind_pass, + "auth_ldap.other_bind.user_dn = username + auth_ldap.other_bind.password = password", + [{rabbitmq_auth_backend_ldap,[{other_bind,{"username","password"}}]}], + [rabbitmq_auth_backend_ldap]}, + + {ssl_options, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.verify = verify_peer + auth_ldap.ssl_options.fail_if_no_peer_cert = true", + [{rabbitmq_auth_backend_ldap, [ + {use_ssl, true}, + {ssl_options, + [{cacertfile, "test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile, "test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile, "test/config_schema_SUITE_data/certs/key.pem"}, + {verify, verify_peer}, + {fail_if_no_peer_cert, true}]} + ]}], + [rabbitmq_auth_backend_ldap]}, + + {ssl_options_verify_peer, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.verify = verify_peer + auth_ldap.ssl_options.fail_if_no_peer_cert = false", + [{rabbitmq_auth_backend_ldap, + [{use_ssl, true}, + {ssl_options, + [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, + {verify,verify_peer}, + {fail_if_no_peer_cert,false}]}]}], + []}, + {ssl_options_password, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.password = t0p$3kRe7", + [{rabbitmq_auth_backend_ldap, + [{use_ssl, true}, + {ssl_options, + [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, + {password,"t0p$3kRe7"}]}]}], + []}, + {ssl_options_tls_versions, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.versions.tls1_2 = tlsv1.2 + auth_ldap.ssl_options.versions.tls1_1 = tlsv1.1", + [], + [{rabbitmq_auth_backend_ldap, + [{ssl_options, + [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, + {versions,['tlsv1.2','tlsv1.1']}]}, + {use_ssl, true}]}], + []}, + {ssl_options_depth, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.depth = 2 + auth_ldap.ssl_options.verify = verify_peer + auth_ldap.ssl_options.fail_if_no_peer_cert = false", + [{rabbitmq_auth_backend_ldap, + [{use_ssl, true}, + {ssl_options, + [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, + {depth,2}, + {verify,verify_peer}, + {fail_if_no_peer_cert,false}]}]}], + []}, + {ssl_options_honor_cipher_order, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.depth = 2 + auth_ldap.ssl_options.verify = verify_peer + auth_ldap.ssl_options.fail_if_no_peer_cert = false + auth_ldap.ssl_options.honor_cipher_order = true", + [{rabbitmq_auth_backend_ldap, + [{use_ssl, true}, + {ssl_options, + [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, + {depth,2}, + {verify,verify_peer}, + {fail_if_no_peer_cert, false}, + {honor_cipher_order, true}]}]}], + []}, + {ssl_options_honor_ecc_order, + "auth_ldap.use_ssl = true + auth_ldap.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/cacert.pem + auth_ldap.ssl_options.certfile = test/config_schema_SUITE_data/certs/cert.pem + auth_ldap.ssl_options.keyfile = test/config_schema_SUITE_data/certs/key.pem + auth_ldap.ssl_options.depth = 2 + auth_ldap.ssl_options.verify = verify_peer + auth_ldap.ssl_options.fail_if_no_peer_cert = false + auth_ldap.ssl_options.honor_ecc_order = true", + [{rabbitmq_auth_backend_ldap, + [{use_ssl, true}, + {ssl_options, + [{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, + {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, + {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, + {depth,2}, + {verify,verify_peer}, + {fail_if_no_peer_cert, false}, + {honor_ecc_order, true}]}]}], + []} + +]. diff --git a/deps/rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl b/deps/rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl new file mode 100644 index 0000000000..d881250040 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/rabbit_ldap_seed.erl @@ -0,0 +1,201 @@ +%% 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-2020 VMware, Inc. or its affiliates. All rights reserved. +%% + +-module(rabbit_ldap_seed). + +-include_lib("eldap/include/eldap.hrl"). + +-export([seed/1,delete/1]). + +seed(Logon) -> + H = connect(Logon), + ok = add(H, rabbitmq_com()), + ok = add(H, ou("people")), + [ add(H, P) || P <- people() ], + ok = add(H, ou("vhosts")), + ok = add(H, test()), + ok = add(H, ou("groups")), + [ add(H, P) || P <- groups() ], + eldap:close(H), + ok. + +rabbitmq_com() -> + {"dc=rabbitmq,dc=com", + [{"objectClass", ["dcObject", "organization"]}, + {"dc", ["rabbitmq"]}, + {"o", ["Test"]}]}. + + +delete(Logon) -> + H = connect(Logon), + eldap:delete(H, "ou=test,dc=rabbitmq,dc=com"), + eldap:delete(H, "ou=test,ou=vhosts,dc=rabbitmq,dc=com"), + eldap:delete(H, "ou=vhosts,dc=rabbitmq,dc=com"), + [ eldap:delete(H, P) || {P, _} <- groups() ], + [ eldap:delete(H, P) || {P, _} <- people() ], + eldap:delete(H, "ou=groups,dc=rabbitmq,dc=com"), + eldap:delete(H, "ou=people,dc=rabbitmq,dc=com"), + eldap:delete(H, "dc=rabbitmq,dc=com"), + eldap:close(H), + ok. + +people() -> + [ bob(), + dominic(), + charlie(), + edward(), + johndoe(), + alice(), + peter(), + carol(), + jimmy() + ]. + +groups() -> + [wheel_group(), + people_group(), + staff_group(), + bobs_group(), + bobs2_group(), + admins_group() + ]. + +wheel_group() -> + {A, _} = alice(), + {C, _} = charlie(), + {D, _} = dominic(), + {P, _} = peter(), + {"cn=wheel,ou=groups,dc=rabbitmq,dc=com", + [{"objectClass", ["groupOfNames"]}, + {"cn", ["wheel"]}, + {"member", [A, C, D, P]}]}. + +people_group() -> + {C, _} = charlie(), + {D, _} = dominic(), + {P, _} = peter(), + {"cn=people,ou=groups,dc=rabbitmq,dc=com", + [{"objectClass", ["groupOfNames"]}, + {"cn", ["people"]}, + {"member", [C, D, P]}]}. + +staff_group() -> + {C, _} = charlie(), + {D, _} = dominic(), + {P, _} = peter(), + {"cn=staff,ou=groups,dc=rabbitmq,dc=com", + [{"objectClass", ["groupOfNames"]}, + {"cn", ["people"]}, + {"member", [C, D, P]}]}. + +bobs_group() -> + {B, _} = bob(), + {"cn=bobs,ou=groups,dc=rabbitmq,dc=com", + [{"objectClass", ["groupOfNames"]}, + {"cn", ["bobs"]}, + {"member", [B]}]}. + +bobs2_group() -> + {B, _} = bobs_group(), + {"cn=bobs2,ou=groups,dc=rabbitmq,dc=com", + [{"objectClass", ["groupOfNames"]}, + {"cn", ["bobs2"]}, + {"member", [B]}]}. + +admins_group() -> + {B, _} = bobs2_group(), + {W, _} = wheel_group(), + {"cn=admins,ou=groups,dc=rabbitmq,dc=com", + [{"objectClass", ["groupOfNames"]}, + {"cn", ["admins"]}, + {"member", [B, W]}]}. + +person(Cn, Sn) -> + {"cn="++Cn++",ou=people,dc=rabbitmq,dc=com", + [{"objectClass", ["person"]}, + {"cn", [Cn]}, + {"sn", [Sn]}, + {"userPassword", ["password"]}]}. + +bob() -> person("Bob", "Robert"). +dominic() -> person("Dominic", "Dom"). +charlie() -> person("Charlie", "Charlie Boy"). +edward() -> person("Edward", "Ed"). +johndoe() -> person("John Doe", "Doe"). + +alice() -> + {"cn=Alice,ou=people,dc=rabbitmq,dc=com", + [{"objectClass", ["person"]}, + {"cn", ["Alice"]}, + {"sn", ["Ali"]}, + {"userPassword", ["password"]}, + {"description", ["can-declare-queues"]}]}. + +peter() -> + {"uid=peter,ou=people,dc=rabbitmq,dc=com", + [{"cn", ["Peter"]}, + {"givenName", ["Peter"]}, + {"sn", ["Jones"]}, + {"uid", ["peter"]}, + {"uidNumber", ["5000"]}, + {"gidNumber", ["10000"]}, + {"homeDirectory", ["/home/peter"]}, + {"mail", ["peter.jones@rabbitmq.com"]}, + {"objectClass", ["top", + "posixAccount", + "shadowAccount", + "inetOrgPerson", + "organizationalPerson", + "person"]}, + {"loginShell", ["/bin/bash"]}, + {"userPassword", ["password"]}, + {"memberOf", ["cn=wheel,ou=groups,dc=rabbitmq,dc=com", + "cn=staff,ou=groups,dc=rabbitmq,dc=com", + "cn=people,ou=groups,dc=rabbitmq,dc=com"]}]}. + +carol() -> + {"uid=carol,ou=people,dc=rabbitmq,dc=com", + [{"cn", ["Carol"]}, + {"givenName", ["Carol"]}, + {"sn", ["Meyers"]}, + {"uid", ["peter"]}, + {"uidNumber", ["655"]}, + {"gidNumber", ["10000"]}, + {"homeDirectory", ["/home/carol"]}, + {"mail", ["carol.meyers@example.com"]}, + {"objectClass", ["top", + "posixAccount", + "shadowAccount", + "inetOrgPerson", + "organizationalPerson", + "person"]}, + {"loginShell", ["/bin/bash"]}, + {"userPassword", ["password"]}]}. + +% rabbitmq/rabbitmq-auth-backend-ldap#100 +jimmy() -> + {"cn=Jimmy,ou=people,dc=rabbitmq,dc=com", + [{"objectClass", ["person"]}, + {"cn", ["Jimmy"]}, + {"sn", ["Makes"]}, + {"userPassword", ["password"]}, + {"description", ["^RMQ-foobar", "^RMQ-.*$"]}]}. + +add(H, {A, B}) -> + ok = eldap:add(H, A, B). + +connect({Host, Port}) -> + {ok, H} = eldap:open([Host], [{port, Port}]), + ok = eldap:simple_bind(H, "cn=admin,dc=rabbitmq,dc=com", "admin"), + H. + +ou(Name) -> + {"ou=" ++ Name ++ ",dc=rabbitmq,dc=com", [{"objectClass", ["organizationalUnit"]}, {"ou", [Name]}]}. + +test() -> + {"ou=test,ou=vhosts,dc=rabbitmq,dc=com", [{"objectClass", ["top", "organizationalUnit"]}, {"ou", ["test"]}]}. + diff --git a/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl new file mode 100644 index 0000000000..34d692ab45 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE.erl @@ -0,0 +1,949 @@ +%% 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-2020 VMware, Inc. or its affiliates. All rights reserved. +%% + +-module(system_SUITE). +-compile([export_all]). + +-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 = <<?ALICE_NAME>>, + password = <<"password">>, + virtual_host = <<?VHOST>>}). + +-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>>}). + +-define(PETER, #amqp_params_network{username = <<?PETER_NAME>>, + password = <<"password">>, + virtual_host = <<?VHOST>>}). + +-define(JIMMY, #amqp_params_network{username = <<?JIMMY_NAME>>, + password = <<"password">>, + virtual_host = <<?VHOST>>}). + +-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}}]} + ]}. + +%%-------------------------------------------------------------------- + +all() -> + [ + {group, non_parallel_tests}, + {group, with_idle_timeout} + ]. + +groups() -> + Tests = [ + purge_connection, + ldap_only, + ldap_and_internal, + internal_followed_ldap_and_internal, + tag_attribution_ldap_only, + tag_attribution_ldap_and_internal, + tag_attribution_internal_followed_by_ldap_and_internal, + invalid_or_clause_ldap_only, + invalid_and_clause_ldap_only, + topic_authorisation_publishing_ldap_only, + topic_authorisation_consumption, + match_bidirectional, + match_bidirectional_gh_100 + ], + [ + {non_parallel_tests, [], Tests + }, + {with_idle_timeout, [], [connections_closed_after_timeout | Tests] + } + ]. + +suite() -> + [{timetrap, {minutes, 2}}]. + +%% ------------------------------------------------------------------- +%% Testsuite setup/teardown. +%% ------------------------------------------------------------------- + +init_per_suite(Config) -> + rabbit_ct_helpers:log_environment(), + rabbit_ct_helpers:run_setup_steps(Config, [fun init_slapd/1]). + +end_per_suite(Config) -> + rabbit_ct_helpers:run_teardown_steps(Config, [fun 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))), + Logon = {"localhost", LdapPort}, + rabbit_ldap_seed:delete(Logon), + rabbit_ldap_seed:seed(Logon), + 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: ~s~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. + +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_internal(Config) -> + ok = control_action(Config, add_user, [?ALICE_NAME, ""]), + ok = control_action(Config, set_permissions, [?ALICE_NAME, "prefix-.*", "prefix-.*", "prefix-.*"]), + ok = control_action(Config, set_user_tags, [?ALICE_NAME, "management", "foo"]), + ok = control_action(Config, add_user, [?BOB_NAME, ""]), + ok = control_action(Config, set_permissions, [?BOB_NAME, "^$", "^$", "^$"]), + ok = control_action(Config, add_user, [?PETER_NAME, ""]), + ok = control_action(Config, set_permissions, [?PETER_NAME, "^$", "^$", "^$"]). + +end_internal(Config) -> + ok = control_action(Config, delete_user, [?ALICE_NAME]), + 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 -> + init_internal(Config), + rabbit_ct_helpers:testcase_started(Config, Testcase); +init_per_testcase(Testcase, Config) + when Testcase == tag_attribution_ldap_and_internal; + Testcase == tag_attribution_internal_followed_by_ldap_and_internal -> + % back up tag queries + Cfg = case rabbit_ct_broker_helpers:rpc(Config, 0, + application, + get_env, + [rabbit_auth_backend_ldap, tag_queries]) of + undefined -> undefined; + {ok, X} -> X + end, + rabbit_ct_helpers:set_config(Config, {tag_queries_config, Cfg}), + internal_authorization_teardown(Config), + internal_authorization_setup(Config), + rabbit_ct_helpers:testcase_started(Config, Testcase); +init_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_started(Config, Testcase). + +end_per_testcase(Testcase, Config) + when Testcase == ldap_and_internal; + Testcase == internal_followed_ldap_and_internal -> + end_internal(Config), + rabbit_ct_helpers:testcase_finished(Config, Testcase); +end_per_testcase(Testcase, Config) + when Testcase == tag_attribution_ldap_and_internal; + Testcase == tag_attribution_internal_followed_by_ldap_and_internal -> + % restore tag queries + Cfg = rabbit_ct_helpers:get_config(Config, tag_queries_config), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, + set_env, + [rabbit_auth_backend_ldap, tag_queries, Cfg]), + internal_authorization_teardown(Config), + rabbit_ct_helpers:testcase_finished(Config, Testcase); +end_per_testcase(connections_closed_after_timeout, Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, + set_env, + [rabbitmq_auth_backend_ldap, + other_bind, anon]), + rabbit_ct_helpers:testcase_finished(Config, connections_closed_after_timeout); +end_per_testcase(Testcase, Config) + when Testcase == invalid_or_clause_ldap_only; + Testcase == invalid_and_clause_ldap_only -> + set_env(Config, vhost_access_query_base_env()), + rabbit_ct_helpers:testcase_finished(Config, Testcase); +end_per_testcase(Testcase, Config) + when Testcase == topic_authorisation_publishing_ldap_only; + Testcase == topic_authorisation_consumption -> + set_env(Config, topic_access_query_base_env()), + rabbit_ct_helpers:testcase_finished(Config, Testcase); +end_per_testcase(Testcase, Config) -> + rabbit_ct_helpers:testcase_finished(Config, Testcase). + + +%% ------------------------------------------------------------------- +%% Testsuite cases +%% ------------------------------------------------------------------- + +purge_connection(Config) -> + {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, + user_login_authentication, + [<<?ALICE_NAME>>, []]), + + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, + purge_connections, []). + +connections_closed_after_timeout(Config) -> + {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, + user_login_authentication, + [<<?ALICE_NAME>>, []]), + + [_] = maps:to_list(get_ldap_connections(Config)), + ct:sleep(idle_timeout(with_idle_timeout) + 200), + + %% There should be no connections after idle timeout + [] = maps:to_list(get_ldap_connections(Config)), + + {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, + user_login_authentication, + [<<?ALICE_NAME>>, []]), + + ct:sleep(round(idle_timeout(with_idle_timeout)/2)), + + %% Login with password opens different connection, + % so unauthorized connection will be closed + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, + set_env, + [rabbitmq_auth_backend_ldap, + other_bind, as_user]), + {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, + user_login_authentication, + [<<?ALICE_NAME>>, + [{password, <<"password">>}]]), + + ct:sleep(round(idle_timeout(with_idle_timeout)/2)), + + {ok, _} = rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, + user_login_authentication, + [<<?ALICE_NAME>>, + [{password, <<"password">>}]]), + + ct:sleep(round(idle_timeout(with_idle_timeout)/2)), + + [{Key, _Conn}] = maps:to_list(get_ldap_connections(Config)), + + %% Key will be {IsAnon, Servers, Options} + %% IsAnon is false for password authorization + {false, _, _} = Key. + + +get_ldap_connections(Config) -> + rabbit_ct_broker_helpers:rpc(Config, 0, + rabbit_auth_backend_ldap, get_connections, []). + +ldap_only(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + login(Config), + in_group(Config), + const(Config), + string_match(Config), + boolean_logic(Config), + tag_check(Config, [monitor]), + tag_check_subst(Config), + logging(Config), + ok. + +ldap_and_internal(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, + [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]]), + login(Config), + permission_match(Config), + tag_check(Config, [monitor, management, foo]), + ok. + +internal_followed_ldap_and_internal(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, + [rabbit_auth_backend_internal, {rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]]), + login(Config), + permission_match(Config), + tag_check(Config, [monitor, management, foo]), + ok. + +tag_attribution_ldap_only(Config) -> + set_env(Config, tag_query_configuration()), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + tag_check(Config, <<"Edward">>, <<"password">>, [monitor, normal]). + +tag_attribution_ldap_and_internal(Config) -> + set_env(Config, tag_query_configuration()), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [{rabbit_auth_backend_ldap, + rabbit_auth_backend_internal}]]), + tag_check(Config, <<"Edward">>, <<"password">>, + [monitor, normal] ++ internal_authorization_tags()). + +tag_attribution_internal_followed_by_ldap_and_internal(Config) -> + set_env(Config, tag_query_configuration()), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_internal, + {rabbit_auth_backend_ldap, + rabbit_auth_backend_internal}]]), + tag_check(Config, <<"Edward">>, <<"password">>, + [monitor, normal] ++ internal_authorization_tags()). + +invalid_or_clause_ldap_only(Config) -> + set_env(Config, vhost_access_query_or_in_group()), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + {ok, C} = amqp_connection:start(B?ALICE), + ok = amqp_connection:close(C). + +invalid_and_clause_ldap_only(Config) -> + set_env(Config, vhost_access_query_and_in_group()), + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + % NB: if the query crashes the ldap plugin it returns {error, access_refused} + % This may not be a reliable return value assertion + {error, not_allowed} = amqp_connection:start(B?ALICE). + +topic_authorisation_publishing_ldap_only(Config) -> + %% topic authorisation at publishing time is enforced in the AMQP channel + %% so it can be tested by sending messages + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + + %% default is to let pass + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), + + %% let pass for topic + set_env(Config, [{topic_access_query, {constant, true}}]), + + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), + + %% check string substitution (on username) + set_env(Config, [{topic_access_query, {for, [{permission, write, {equals, "${username}", "Alice"}}, + {permission, read, {constant, false}} + ]}}]), + test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), + test_publish(P?BOB, <<"amq.topic">>, <<"a.b.c">>, fail), + + %% check string substitution on routing key (with regex) + set_env(Config, [{topic_access_query, {for, [{permission, write, {'and', + [{equals, "${username}", "Alice"}, + {match, {string, "${routing_key}"}, {string, "^a"}}] + }}, + {permission, read, {constant, false}} + ]}}]), + %% user and routing key OK + test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), + %% user and routing key OK + test_publish(P?ALICE, <<"amq.topic">>, <<"a.c">>, ok), + %% user OK, routing key KO, should fail + test_publish(P?ALICE, <<"amq.topic">>, <<"b.c">>, fail), + %% user KO, routing key OK, should fail + test_publish(P?BOB, <<"amq.topic">>, <<"a.b.c">>, fail), + + ok. + +topic_authorisation_consumption(Config) -> + %% topic authorisation for consumption isn't enforced in AMQP + %% (it is in plugins like STOMP and MQTT, at subscription time) + %% so we directly test the LDAP backend, inside the broker + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + ?MODULE, topic_authorisation_consumption1, [Config]). + +topic_authorisation_consumption1(Config) -> + %% we can't use the LDAP backend record here, falling back to simple tuples + Alice = {auth_user,<<"Alice">>, [monitor], + {impl,"cn=Alice,ou=People,dc=rabbitmq,dc=com",<<"password">>} + }, + Bob = {auth_user,<<"Bob">>, [monitor], + {impl,"cn=Bob,ou=People,dc=rabbitmq,dc=com",<<"password">>} + }, + Resource = #resource{virtual_host = <<"/">>, name = <<"amq.topic">>, kind = topic}, + Context = #{routing_key => <<"a.b">>, + variable_map => #{ + <<"username">> => <<"guest">>, + <<"vhost">> => <<"other-vhost">> + }}, + %% default is to let pass + true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, Context), + + %% let pass for topic + set_env(Config, [{topic_access_query, {for, [{permission, read, {constant, true}}, + {permission, write, {constant, false}}] + }}]), + + true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, Context), + + %% check string substitution (on username) + set_env(Config, [{topic_access_query, {for, [{permission, read, {equals, "${username}", "Alice"}}, + {permission, write, {constant, false}}] + }}]), + + true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, Context), + false = rabbit_auth_backend_ldap:check_topic_access(Bob, Resource, read, Context), + + %% check string substitution on routing key (with regex) + set_env(Config, [{topic_access_query, {for, [{permission, read, {'and', + [{equals, "${username}", "Alice"}, + {match, {string, "${routing_key}"}, {string, "^a"}}] + }}, + {permission, write, {constant, false}}] + }}]), + %% user and routing key OK + true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, #{routing_key => <<"a.b.c">>}), + %% user and routing key OK + true = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, #{routing_key => <<"a.c">>}), + %% user OK, routing key KO, should fail + false = rabbit_auth_backend_ldap:check_topic_access(Alice, Resource, read, #{routing_key => <<"b.c">>}), + %% user KO, routing key OK, should fail + false = rabbit_auth_backend_ldap:check_topic_access(Bob, Resource, read, #{routing_key => <<"a.b.c">>}), + ok. + +match_bidirectional(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + + Configurations = [ + fun resource_access_query_match/0, + fun resource_access_query_match_query_is_string/0, + fun resource_access_query_match_re_query_is_string/0, + fun resource_access_query_match_query_and_re_query_are_strings/0 + ], + + [begin + set_env(Config, ConfigurationFunction()), + Q1 = [#'queue.declare'{queue = <<"Alice-queue">>}], + Q2 = [#'queue.declare'{queue = <<"Ali">>}], + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + [test_resource(PTR) || PTR <- [{P?ALICE, Q1, ok}, + {P?ALICE, Q2, fail}]] + end || ConfigurationFunction <- Configurations], + ok. + +match_bidirectional_gh_100(Config) -> + ok = rabbit_ct_broker_helpers:rpc(Config, 0, + application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), + + Configurations = [ + fun resource_access_query_match_gh_100/0, + fun resource_access_query_match_query_is_string_gh_100/0 + ], + + [begin + set_env(Config, ConfigurationFunction()), + Q1 = [#'queue.declare'{queue = <<"Jimmy-queue">>}], + Q2 = [#'queue.declare'{queue = <<"Jimmy">>}], + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + [test_resource(PTR) || PTR <- [{P?JIMMY, Q1, ok}, + {P?JIMMY, Q2, ok}]] + end || ConfigurationFunction <- Configurations], + ok. + +%%-------------------------------------------------------------------- + +test_publish(Person, Exchange, RoutingKey, ExpectedResult) -> + {ok, Connection} = amqp_connection:start(Person), + {ok, Channel} = amqp_connection:open_channel(Connection), + ActualResult = + try + Publish = #'basic.publish'{exchange = Exchange, routing_key = RoutingKey}, + amqp_channel:cast(Channel, Publish, #amqp_msg{payload = <<"foobar">>}), + amqp_channel:call(Channel, #'basic.qos'{prefetch_count = 0}), + ok + catch exit:_ -> fail + after + amqp_connection:close(Connection) + end, + ExpectedResult = ActualResult. + +login(Config) -> + lists:flatten( + [test_login(Config, {N, Env}, L, FilterList, case {LGood, EnvGood} of + {good, good} -> fun succ/1; + _ -> fun fail/1 + end) || + {LGood, FilterList, L, _Tags} <- logins(Config), + {N, {EnvGood, Env}} <- login_envs()]). + +logins(Config) -> logins_network(Config) ++ logins_direct(Config). + +%% Format for login tests, {Outcome, FilterList, Login, Tags}. +%% Tests skipped for each login_env reference in FilterList. +logins_network(Config) -> + B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + [{bad, [5, 6], B#amqp_params_network{}, []}, + {bad, [5, 6], B#amqp_params_network{username = <<?ALICE_NAME>>}, []}, + {bad, [5, 6], B#amqp_params_network{username = <<?ALICE_NAME>>, + password = <<"password">>}, []}, + {bad, [5, 6], B#amqp_params_network{username = <<"Alice">>, + password = <<"Alicja">>, + virtual_host = <<?VHOST>>}, []}, + {bad, [1, 2, 3, 4, 6, 7], B?CAROL, []}, + {good, [5, 6], B?ALICE, []}, + {good, [5, 6], B?BOB, []}, + {good, [1, 2, 3, 4, 6, 7, 8], B?PETER, []}]. + +logins_direct(Config) -> + N = #amqp_params_direct{node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename)}, + [{bad, [5], N#amqp_params_direct{}, []}, + {bad, [5], N#amqp_params_direct{username = <<?ALICE_NAME>>}, []}, + {bad, [5], N#amqp_params_direct{username = <<?ALICE_NAME>>, + password = <<"password">>}, [management]}, + {good, [5], N#amqp_params_direct{username = <<?ALICE_NAME>>, + password = <<"password">>, + virtual_host = <<?VHOST>>}, [management]}]. + +%% Format for login envs, {Reference, {Outcome, Env}} +login_envs() -> + [{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()}}, + {8, {good, vhost_access_query_nested_groups_env()}}]. + +base_login_env() -> + [{user_dn_pattern, "cn=${username},ou=People,dc=rabbitmq,dc=com"}, + {dn_lookup_attribute, none}, + {dn_lookup_base, none}, + {dn_lookup_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=rabbitmq,dc=com"}}, + {log, true}]. + +%% TODO configure OpenLDAP to allow a dn_lookup_post_bind_env() +dn_lookup_pre_bind_env() -> + [{user_dn_pattern, "${username}"}, + {dn_lookup_attribute, "cn"}, + {dn_lookup_base, "OU=People,DC=rabbitmq,DC=com"}, + {dn_lookup_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. + +other_bind_admin_env() -> + [{other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. + +other_bind_anon_env() -> + [{other_bind, anon}]. + +other_bind_broken_env() -> + [{other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admi"}}]. + +tag_queries_subst_env() -> + [{tag_queries, [{administrator, {constant, false}}, + {management, + {exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}}]}]. + +posix_vhost_access_multiattr_env() -> + [{user_dn_pattern, "uid=${username},ou=People,dc=rabbitmq,dc=com"}, + {vhost_access_query, + {'and', [{exists, "ou=${vhost},ou=vhosts,dc=rabbitmq,dc=com"}, + {equals, + {attribute, "${user_dn}","memberOf"}, + {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}}, + {equals, + {attribute, "${user_dn}","memberOf"}, + {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}}, + {equals, + {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}, + {attribute,"${user_dn}","memberOf"}}, + {equals, + {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}, + {attribute, "${user_dn}","memberOf"}}, + {match, + {attribute, "${user_dn}","memberOf"}, + {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}}, + {match, + {attribute, "${user_dn}","memberOf"}, + {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}}, + {match, + {string, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"}, + {attribute, "${user_dn}","memberOf"}}, + {match, + {string, "cn=people,ou=groups,dc=rabbitmq,dc=com"}, + {attribute, "${user_dn}","memberOf"}} + ]}}]. + +vhost_access_query_or_in_group() -> + [{vhost_access_query, + {'or', [ + {in_group, "cn=bananas,ou=groups,dc=rabbitmq,dc=com"}, + {in_group, "cn=apples,ou=groups,dc=rabbitmq,dc=com"}, + {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} + ]}}]. + +vhost_access_query_and_in_group() -> + [{vhost_access_query, + {'and', [ + {in_group, "cn=bananas,ou=groups,dc=rabbitmq,dc=com"}, + {in_group, "cn=wheel,ou=groups,dc=rabbitmq,dc=com"} + ]}}]. + +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"}. + +resource_access_query_match_gh_100() -> + [{resource_access_query, + {match, {string, "RMQ-${vhost}"}, {attribute, "${user_dn}", "description"}} + }]. + +resource_access_query_match_query_is_string_gh_100() -> + [{resource_access_query, + {match, "RMQ-${vhost}", {attribute, "${user_dn}", "description"}} + }]. + +resource_access_query_match() -> + [{resource_access_query, {match, {string, "${name}"}, + {string, "^${username}-"}} + }]. + +resource_access_query_match_query_is_string() -> + [{resource_access_query, {match, "${name}", + {string, "^${username}-"}} + }]. + +resource_access_query_match_re_query_is_string() -> + [{resource_access_query, {match, {string, "${name}"}, + "^${username}-"} + }]. + +resource_access_query_match_query_and_re_query_are_strings() -> + [{resource_access_query, {match, "${name}", + "^${username}-"} + }]. + +topic_access_query_base_env() -> + [{topic_access_query, topic_access_query_base()}]. + +topic_access_query_base() -> + {constant, true}. + +test_login(Config, {N, Env}, Login, FilterList, ResultFun) -> + case lists:member(N, FilterList) of + true -> []; + _ -> + try + set_env(Config, Env), + ResultFun(Login) + after + set_env(Config, base_login_env()) + end + end. + +rpc_set_env(Config, Args) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, set_env, Args). + +set_env(Config, Env) -> + [rpc_set_env(Config, [rabbitmq_auth_backend_ldap, K, V]) || {K, V} <- Env]. + +succ(Login) -> + {ok, Pid} = amqp_connection:start(Login), + amqp_connection:close(Pid). +fail(Login) -> ?assertMatch({error, _}, amqp_connection:start(Login)). + +%%-------------------------------------------------------------------- + +in_group(Config) -> + X = [#'exchange.declare'{exchange = <<"test">>}], + B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + test_resources([{B?ALICE, X, ok}, + {B?BOB, X, fail}]). + +const(Config) -> + Q = [#'queue.declare'{queue = <<"test">>}], + B = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + test_resources([{B?ALICE, Q, ok}, + {B?BOB, Q, fail}]). + +string_match(Config) -> + B = fun(N) -> + [#'exchange.declare'{exchange = N}, + #'queue.declare'{queue = <<"test">>}, + #'queue.bind'{exchange = N, queue = <<"test">>}] + end, + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + test_resources([{P?ALICE, B(<<"xch-Alice-abc123">>), ok}, + {P?ALICE, B(<<"abc123">>), fail}, + {P?ALICE, B(<<"xch-Someone Else-abc123">>), fail}]). + +boolean_logic(Config) -> + Q1 = [#'queue.declare'{queue = <<"test1">>}, + #'basic.consume'{queue = <<"test1">>}], + Q2 = [#'queue.declare'{queue = <<"test2">>}, + #'basic.consume'{queue = <<"test2">>}], + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + [test_resource(PTR) || PTR <- [{P?ALICE, Q1, ok}, + {P?ALICE, Q2, ok}, + {P?BOB, Q1, fail}, + {P?BOB, Q2, fail}]]. + +permission_match(Config) -> + B = fun(N) -> + [#'exchange.declare'{exchange = N}, + #'queue.declare'{queue = <<"prefix-test">>}, + #'queue.bind'{exchange = N, queue = <<"prefix-test">>}] + end, + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + test_resources([{P?ALICE, B(<<"prefix-abc123">>), ok}, + {P?ALICE, B(<<"abc123">>), fail}, + {P?ALICE, B(<<"xch-Alice-abc123">>), fail}]). + +%% Tag check tests, with substitution +tag_check_subst(Config) -> + lists:flatten( + [test_tag_check(Config, tag_queries_subst_env(), + fun () -> tag_check(Config, Username, Password, VHost, Outcome, Tags) end) || + {Outcome, _FilterList, #amqp_params_direct{username = Username, + password = Password, + virtual_host = VHost}, + Tags} <- logins_direct(Config)]). + +%% Tag check +tag_check(Config, Tags) -> + tag_check(Config, <<?ALICE_NAME>>, <<"password">>, Tags). + +tag_check(Config, Username, Password, Tags) -> + tag_check(Config, Username, Password, <<>>, good, Tags). + +tag_check(Config, Username, Password, VHost, Outcome, Tags) + when is_binary(Username), is_binary(Password), is_binary(VHost), is_list(Tags) -> + {ok, User} = rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_access_control, check_user_login, [Username, [{password, Password}, {vhost, VHost}]]), + tag_check_outcome(Outcome, Tags, User); +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(Config, Env, TagCheckFun) -> + try + set_env(Config, Env), + TagCheckFun() + after + set_env(Config, 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=rabbitmq,dc=com"}}]}]. + +internal_authorization_setup(Config) -> + ok = control_action(Config, add_user, ["Edward", ""]), + ok = control_action(Config, set_user_tags, ["Edward"] ++ + [ atom_to_list(T) || T <- internal_authorization_tags() ]). + +internal_authorization_teardown(Config) -> + control_action(Config, delete_user, ["Edward"]). + +internal_authorization_tags() -> + [foo, bar]. + +%% Logging tests, triggered within 'test_login/4' +logging(Config) -> + lists:flatten( + [test_login(Config, {N, Env}, L, FilterList, case {LGood, EnvGood} of + {good, good} -> fun succ/1; + _ -> fun fail/1 + end) || + {LGood, FilterList, L} <- logging_test_users(Config), + {N, {EnvGood, Env}} <- logging_envs()]). + +%% Format for logging tests, {Outcome, FilterList, Login}. +logging_test_users(Config) -> + P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, + [{bad, [], P#amqp_params_network{username = <<?ALICE_NAME>>}}, + {good, [], P?ALICE}]. + +logging_envs() -> + [{1, {good, scrub_bind_creds_env()}}, + {2, {good, display_bind_creds_env()}}, + {3, {bad, scrub_bind_single_cred_env()}}, + {4, {bad, scrub_bind_creds_no_equals_env()}}, + {5, {bad, scrub_bind_creds_no_seperator_env()}}]. + +scrub_bind_creds_env() -> + [{log, network}, + {other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. + +display_bind_creds_env() -> + [{log, network_unsafe}, + {other_bind, {"cn=admin,dc=rabbitmq,dc=com", "admin"}}]. + +scrub_bind_single_cred_env() -> + [{log, network}, + {other_bind, {"dc=com", "admin"}}]. + +scrub_bind_creds_no_equals_env() -> + [{log, network}, + {other_bind, {"cn*admin,dc>rabbitmq,dc&com", "admin"}}]. + +scrub_bind_creds_no_seperator_env() -> + [{log, network}, + {other_bind, {"cn=admindc=rabbitmqdc&com", "admin"}}]. + +%%-------------------------------------------------------------------- + +test_resources(PTRs) -> [test_resource(PTR) || PTR <- PTRs]. + +test_resource({Person, Things, Result}) -> + {ok, Conn} = amqp_connection:start(Person), + {ok, Ch} = amqp_connection:open_channel(Conn), + ?assertEqual(Result, + try + [amqp_channel:call(Ch, T) || T <- Things], + ok + catch exit:_ -> fail + after + amqp_connection:close(Conn) + end). + +control_action(Config, Command, Args) -> + Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + control_action(Config, Command, Node, Args, default_options()). + +control_action(Config, Command, Args, NewOpts) -> + Node = rabbit_ct_broker_helpers:get_node_config(Config, 0, nodename), + control_action(Config, Command, Node, Args, + expand_options(default_options(), NewOpts)). + +control_action(_Config, Command, Node, Args, Opts) -> + case rabbit_control_helper:command(Command, Node, Args, Opts) of + ok -> + io:format("done.~n"), + ok; + Other -> + io:format("failed.~n"), + Other + end. + +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; + false -> [A | R] + end + end, Bs, As). + 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 100755 index 0000000000..c1319898b2 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/system_SUITE_data/init-slapd.sh @@ -0,0 +1,98 @@ +#!/bin/sh +# vim:sw=4:et: + +set -ex + +slapd_data_dir=$1 +tcp_port=$2 + +pidfile="$slapd_data_dir/slapd.pid" +uri="ldap://localhost:$tcp_port" + +binddn="cn=config" +passwd=secret + +case "$(uname -s)" in + Linux) + slapd=/usr/sbin/slapd + modulepath=/usr/lib/ldap + schema_dir=/etc/ldap/schema + ;; + FreeBSD) + slapd=/usr/local/libexec/slapd + modulepath=/usr/local/libexec/openldap + schema_dir=/usr/local/etc/openldap/schema + ;; + *) + exit 1 + ;; +esac + +# -------------------------------------------------------------------- +# slapd(8) configuration + start +# -------------------------------------------------------------------- + +rm -rf "$slapd_data_dir" +mkdir -p "$slapd_data_dir" + +conf_file=$slapd_data_dir/slapd.conf +cat <<EOF > "$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" + +conf_dir=$slapd_data_dir/slapd.d +mkdir -p "$conf_dir" + +# Start slapd(8). +"$slapd" \ + -f "$conf_file" \ + -F "$conf_dir" \ + -h "$uri" + +auth="-x -D $binddn -w $passwd" + +# We wait for the server to start. +for seconds in 1 2 3 4 5 6 7 8 9 10; do + ldapsearch $auth -H "$uri" -LLL -b cn=config dn && break; + sleep 1 +done + +# -------------------------------------------------------------------- +# Load the example LDIFs for the testsuite. +# -------------------------------------------------------------------- + +script_dir=$(cd "$(dirname "$0")" && pwd) +example_ldif_dir="$script_dir/../../example" +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. +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. +sed -E -e "s,^olcModulePath:.*,olcModulePath: $modulepath," \ + < "$example_ldif_dir/memberof_init.ldif" | \ + ldapadd $auth -H "$uri" + +ldapmodify $auth -H "$uri" -f "$example_ldif_dir/refint_1.ldif" +ldapadd $auth -H "$uri" -f "$example_ldif_dir/refint_2.ldif" + +ldapsearch $auth -H "$uri" -LLL -b cn=config dn + +echo SLAPD_PID=$(cat "$pidfile") diff --git a/deps/rabbitmq_auth_backend_ldap/test/unit_SUITE.erl b/deps/rabbitmq_auth_backend_ldap/test/unit_SUITE.erl new file mode 100644 index 0000000000..af318615f8 --- /dev/null +++ b/deps/rabbitmq_auth_backend_ldap/test/unit_SUITE.erl @@ -0,0 +1,47 @@ +%% 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-2020 VMware, Inc. or its affiliates. All rights reserved. +%% + +-module(unit_SUITE). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +-compile([export_all]). + +all() -> + [ + fill, + ad_fill + ]. + +fill(_Config) -> + F = fun(Fmt, Args, Res) -> + ?assertEqual(Res, rabbit_auth_backend_ldap_util:fill(Fmt, Args)) + end, + F("x${username}x", [{username, "ab"}], "xabx"), + F("x${username}x", [{username, ab}], "xabx"), + F("x${username}x", [{username, <<"ab">>}], "xabx"), + F("x${username}x", [{username, ""}], "xx"), + F("x${username}x", [{fusername, "ab"}], "x${username}x"), + F("x${usernamex", [{username, "ab"}], "x${usernamex"), + F("x${username}x", [{username, "a\\b"}], "xa\\bx"), + F("x${username}x", [{username, "a&b"}], "xa&bx"), + ok. + +ad_fill(_Config) -> + F = fun(Fmt, Args, Res) -> + ?assertEqual(Res, rabbit_auth_backend_ldap_util:fill(Fmt, Args)) + end, + + U0 = <<"ADDomain\\ADUser">>, + A0 = rabbit_auth_backend_ldap_util:get_active_directory_args(U0), + F("x-${ad_domain}-x-${ad_user}-x", A0, "x-ADDomain-x-ADUser-x"), + + U1 = <<"ADDomain\\ADUser\\Extra">>, + A1 = rabbit_auth_backend_ldap_util:get_active_directory_args(U1), + F("x-${ad_domain}-x-${ad_user}-x", A1, "x-ADDomain-x-ADUser\\Extra-x"), + ok. |