summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/rabbit_access_control.erl14
-rw-r--r--src/rabbit_table.erl10
-rw-r--r--src/rabbit_upgrade_functions.erl7
-rw-r--r--src/rabbit_vhost.erl3
-rw-r--r--test/topic_permission_SUITE.erl217
-rw-r--r--test/unit_SUITE.erl4
6 files changed, 253 insertions, 2 deletions
diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl
index 3ae7d7f690..4dff2dbede 100644
--- a/src/rabbit_access_control.erl
+++ b/src/rabbit_access_control.erl
@@ -19,7 +19,7 @@
-include("rabbit.hrl").
-export([check_user_pass_login/2, check_user_login/2, check_user_loopback/2,
- check_vhost_access/3, check_resource_access/3]).
+ check_vhost_access/3, check_resource_access/3, check_topic_access/4]).
%%----------------------------------------------------------------------------
@@ -161,6 +161,18 @@ check_resource_access(User = #user{username = Username,
(_, Else) -> Else
end, ok, Modules).
+check_topic_access(User = #user{username = Username,
+ authz_backends = Modules},
+ Resource, Permission, Context) ->
+ lists:foldl(
+ fun({Module, Impl}, ok) ->
+ check_access(
+ fun() -> Module:check_topic_access(
+ auth_user(User, Impl), Resource, Permission, Context) end,
+ Module, "access to topic '~s' in exchange ~s refused for user '~s'",
+ [maps:get(routing_key, Context), rabbit_misc:rs(Resource), Username]);
+ (_, Else) -> Else
+ end, ok, Modules).
check_access(Fun, Module, ErrStr, ErrArgs) ->
check_access(Fun, Module, ErrStr, ErrArgs, access_refused).
diff --git a/src/rabbit_table.erl b/src/rabbit_table.erl
index cae47c08a9..040075ea87 100644
--- a/src/rabbit_table.erl
+++ b/src/rabbit_table.erl
@@ -21,6 +21,9 @@
check_schema_integrity/1, clear_ram_only_tables/0, retry_timeout/0,
wait_for_replicated/0]).
+%% for testing purposes
+-export([definitions/0]).
+
-include("rabbit.hrl").
%%----------------------------------------------------------------------------
@@ -272,6 +275,13 @@ definitions() ->
{match, #user_permission{user_vhost = #user_vhost{_='_'},
permission = #permission{_='_'},
_='_'}}]},
+ {rabbit_topic_permission,
+ [{record_name, topic_permission},
+ {attributes, record_info(fields, topic_permission)},
+ {disc_copies, [node()]},
+ {match, #topic_permission{topic_permission_key = #topic_permission_key{_='_'},
+ pattern = '_',
+ _='_'}}]},
{rabbit_vhost,
[{record_name, vhost},
{attributes, record_info(fields, vhost)},
diff --git a/src/rabbit_upgrade_functions.erl b/src/rabbit_upgrade_functions.erl
index a53ad0c8f9..c2a1e49b32 100644
--- a/src/rabbit_upgrade_functions.erl
+++ b/src/rabbit_upgrade_functions.erl
@@ -58,6 +58,7 @@
-rabbit_upgrade({operator_policies, mnesia, [slave_pids_pending_shutdown, internal_system_x]}).
-rabbit_upgrade({vhost_limits, mnesia, []}).
-rabbit_upgrade({queue_vhost_field, mnesia, [operator_policies]}).
+-rabbit_upgrade({topic_permission, mnesia, []}).
%% -------------------------------------------------------------------
@@ -564,6 +565,12 @@ user_password_hashing() ->
end,
[username, password_hash, tags, hashing_algorithm]).
+topic_permission() ->
+ create(rabbit_topic_permission,
+ [{record_name, topic_permission},
+ {attributes, [topic_permission_key, pattern]},
+ {disc_copies, [node()]}]).
+
%%--------------------------------------------------------------------
transform(TableName, Fun, FieldList) ->
diff --git a/src/rabbit_vhost.erl b/src/rabbit_vhost.erl
index 26b8143fec..6edb62425b 100644
--- a/src/rabbit_vhost.erl
+++ b/src/rabbit_vhost.erl
@@ -121,6 +121,9 @@ internal_delete(VHostPath) ->
[ok = rabbit_auth_backend_internal:clear_permissions(
proplists:get_value(user, Info), VHostPath)
|| Info <- rabbit_auth_backend_internal:list_vhost_permissions(VHostPath)],
+ TopicPermissions = rabbit_auth_backend_internal:list_vhost_topic_permissions(VHostPath),
+ [ok = rabbit_auth_backend_internal:clear_topic_permissions(
+ proplists:get_value(user, TopicPermission), VHostPath) || TopicPermission <- TopicPermissions],
Fs1 = [rabbit_runtime_parameters:clear(VHostPath,
proplists:get_value(component, Info),
proplists:get_value(name, Info))
diff --git a/test/topic_permission_SUITE.erl b/test/topic_permission_SUITE.erl
new file mode 100644
index 0000000000..b7d65e6d0c
--- /dev/null
+++ b/test/topic_permission_SUITE.erl
@@ -0,0 +1,217 @@
+%% The contents of this file are subject to the Mozilla Public License
+%% Version 1.1 (the "License"); you may not use this file except in
+%% compliance with the License. You may obtain a copy of the License at
+%% http://www.mozilla.org/MPL/
+%%
+%% Software distributed under the License is distributed on an "AS IS"
+%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
+%% License for the specific language governing rights and limitations
+%% under the License.
+%%
+%% The Original Code is RabbitMQ.
+%%
+%% The Initial Developer of the Original Code is GoPivotal, Inc.
+%% Copyright (c) 2011-2016 Pivotal Software, Inc. All rights reserved.
+%%
+
+-module(topic_permission_SUITE).
+
+-include_lib("common_test/include/ct.hrl").
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-compile(export_all).
+
+all() ->
+ [
+ {group, sequential_tests}
+ ].
+
+groups() -> [
+ {sequential_tests, [], [
+ topic_permission_database_access,
+ topic_permission_checks
+ ]}
+ ].
+
+init_per_suite(Config) ->
+ rabbit_ct_helpers:log_environment(),
+ Config1 = rabbit_ct_helpers:set_config(Config, [
+ {rmq_nodename_suffix, ?MODULE}
+ ]),
+ rabbit_ct_helpers:run_setup_steps(Config1,
+ rabbit_ct_broker_helpers:setup_steps() ++
+ rabbit_ct_client_helpers:setup_steps()).
+
+end_per_suite(Config) ->
+ rabbit_ct_helpers:run_teardown_steps(Config,
+ rabbit_ct_client_helpers:teardown_steps() ++
+ rabbit_ct_broker_helpers:teardown_steps()).
+
+init_per_group(_, Config) -> Config.
+end_per_group(_, Config) -> Config.
+
+init_per_testcase(Testcase, Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0,
+ ?MODULE, clear_tables, []),
+ rabbit_ct_helpers:testcase_started(Config, Testcase).
+
+clear_tables() ->
+ {atomic, ok} = mnesia:clear_table(rabbit_topic_permission),
+ {atomic, ok} = mnesia:clear_table(rabbit_vhost),
+ {atomic, ok} = mnesia:clear_table(rabbit_user),
+ ok.
+
+end_per_testcase(Testcase, Config) ->
+ rabbit_ct_helpers:testcase_finished(Config, Testcase).
+
+topic_permission_database_access(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0,
+ ?MODULE, topic_permission_database_access1, [Config]).
+
+topic_permission_database_access1(_Config) ->
+ 0 = length(ets:tab2list(rabbit_topic_permission)),
+ rabbit_vhost:add(<<"/">>),
+ rabbit_vhost:add(<<"other-vhost">>),
+ rabbit_auth_backend_internal:add_user(<<"guest">>, <<"guest">>),
+ rabbit_auth_backend_internal:add_user(<<"dummy">>, <<"dummy">>),
+
+ rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"/">>, <<"amq.topic">>, "^a"
+ ),
+ 1 = length(ets:tab2list(rabbit_topic_permission)),
+ 1 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ 0 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"dummy">>)),
+ 1 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"/">>)),
+ 0 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"other-vhost">>)),
+ 1 = length(rabbit_auth_backend_internal:list_user_vhost_topic_permissions(<<"guest">>,<<"/">>)),
+ 0 = length(rabbit_auth_backend_internal:list_user_vhost_topic_permissions(<<"guest">>,<<"other-vhost">>)),
+ 1 = length(rabbit_auth_backend_internal:list_topic_permissions()),
+
+ rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"other-vhost">>, <<"amq.topic">>, ".*"
+ ),
+ 2 = length(ets:tab2list(rabbit_topic_permission)),
+ 2 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ 0 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"dummy">>)),
+ 1 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"/">>)),
+ 1 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"other-vhost">>)),
+ 1 = length(rabbit_auth_backend_internal:list_user_vhost_topic_permissions(<<"guest">>,<<"/">>)),
+ 1 = length(rabbit_auth_backend_internal:list_user_vhost_topic_permissions(<<"guest">>,<<"other-vhost">>)),
+ 2 = length(rabbit_auth_backend_internal:list_topic_permissions()),
+
+ rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"/">>, <<"topic1">>, "^a"
+ ),
+ rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"/">>, <<"topic2">>, "^a"
+ ),
+
+ 4 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ 3 = length(rabbit_auth_backend_internal:list_user_vhost_topic_permissions(<<"guest">>,<<"/">>)),
+ 1 = length(rabbit_auth_backend_internal:list_user_vhost_topic_permissions(<<"guest">>,<<"other-vhost">>)),
+ 4 = length(rabbit_auth_backend_internal:list_topic_permissions()),
+
+ rabbit_auth_backend_internal:clear_topic_permissions(<<"guest">>, <<"other-vhost">>),
+ 0 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"other-vhost">>)),
+ 3 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ rabbit_auth_backend_internal:clear_topic_permissions(<<"guest">>, <<"/">>, <<"topic1">>),
+ 2 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ rabbit_auth_backend_internal:clear_topic_permissions(<<"guest">>, <<"/">>),
+ 0 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+
+
+ {error, {no_such_user, _}} = (catch rabbit_auth_backend_internal:set_topic_permissions(
+ <<"non-existing-user">>, <<"other-vhost">>, <<"amq.topic">>, ".*"
+ )),
+
+ {error, {no_such_vhost, _}} = (catch rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"non-existing-vhost">>, <<"amq.topic">>, ".*"
+ )),
+
+ {error, {no_such_user, _}} = (catch rabbit_auth_backend_internal:set_topic_permissions(
+ <<"non-existing-user">>, <<"non-existing-vhost">>, <<"amq.topic">>, ".*"
+ )),
+
+ {error, {no_such_user, _}} = (catch rabbit_auth_backend_internal:list_user_topic_permissions(
+ "non-existing-user"
+ )),
+
+ {error, {no_such_vhost, _}} = (catch rabbit_auth_backend_internal:list_vhost_topic_permissions(
+ "non-existing-vhost"
+ )),
+
+ {error, {invalid_regexp, _, _}} = (catch rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"/">>, <<"amq.topic">>, "["
+ )),
+ ok.
+
+topic_permission_checks(Config) ->
+ ok = rabbit_ct_broker_helpers:rpc(Config, 0,
+ ?MODULE, topic_permission_checks1, [Config]).
+
+topic_permission_checks1(_Config) ->
+ 0 = length(ets:tab2list(rabbit_topic_permission)),
+ rabbit_misc:execute_mnesia_transaction(fun() ->
+ ok = mnesia:write(rabbit_vhost,
+ #vhost{virtual_host = <<"/">>},
+ write),
+ ok = mnesia:write(rabbit_vhost,
+ #vhost{virtual_host = <<"other-vhost">>},
+ write)
+ end),
+ rabbit_auth_backend_internal:add_user(<<"guest">>, <<"guest">>),
+ rabbit_auth_backend_internal:add_user(<<"dummy">>, <<"dummy">>),
+
+ rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"/">>, <<"amq.topic">>, "^a"
+ ),
+ 1 = length(ets:tab2list(rabbit_topic_permission)),
+ 1 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ 0 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"dummy">>)),
+ 1 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"/">>)),
+ 0 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"other-vhost">>)),
+
+ rabbit_auth_backend_internal:set_topic_permissions(
+ <<"guest">>, <<"other-vhost">>, <<"amq.topic">>, ".*"
+ ),
+ 2 = length(ets:tab2list(rabbit_topic_permission)),
+ 2 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"guest">>)),
+ 0 = length(rabbit_auth_backend_internal:list_user_topic_permissions(<<"dummy">>)),
+ 1 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"/">>)),
+ 1 = length(rabbit_auth_backend_internal:list_vhost_topic_permissions(<<"other-vhost">>)),
+
+ User = #auth_user{username = <<"guest">>},
+ Topic = #resource{name = <<"amq.topic">>, virtual_host = <<"/">>,
+ kind = topic},
+ Context = #{routing_key => <<"a.b.c">>},
+ %% user has access to exchange, routing key matches
+ true = rabbit_auth_backend_internal:check_topic_access(
+ User,
+ Topic,
+ write,
+ Context
+ ),
+ %% user has access to exchange, routing key does not match
+ false = rabbit_auth_backend_internal:check_topic_access(
+ User,
+ Topic,
+ write,
+ #{routing_key => <<"x.y.z">>}
+ ),
+ %% user has access to exchange but not on this vhost
+ %% let pass when there's no match
+ true = rabbit_auth_backend_internal:check_topic_access(
+ User,
+ Topic#resource{virtual_host = <<"fancyvhost">>},
+ write,
+ Context
+ ),
+ %% user does not have access to exchange
+ %% let pass when there's no match
+ true = rabbit_auth_backend_internal:check_topic_access(
+ #auth_user{username = <<"dummy">>},
+ Topic,
+ write,
+ Context
+ ),
+ ok. \ No newline at end of file
diff --git a/test/unit_SUITE.erl b/test/unit_SUITE.erl
index 8499fd2abc..f3fec06cb4 100644
--- a/test/unit_SUITE.erl
+++ b/test/unit_SUITE.erl
@@ -83,7 +83,7 @@ init_per_testcase(TC, Config) when TC =:= decrypt_start_app;
TC =:= decrypt_start_app_undefined ->
application:load(rabbit),
Config;
-init_per_testcase(_, Config) ->
+init_per_testcase(_Testcase, Config) ->
Config.
end_per_testcase(TC, _Config) when TC =:= decrypt_start_app;
@@ -464,6 +464,8 @@ rabbitmqctl_encode_encrypt_decrypt(Secret) ->
)
.
+
+
%% -------------------------------------------------------------------
%% pg_local.
%% -------------------------------------------------------------------