Implement check_topic_access callback

References rabbitmq/rabbitmq-server#505
This commit is contained in:
Arnaud Cogoluègnes 2016-12-29 08:55:58 +01:00
parent c087a5419e
commit aa1bf987c8
3 changed files with 42 additions and 65 deletions

View File

@ -13,6 +13,7 @@ define PROJECT_ENV
{other_bind, as_user}, {other_bind, as_user},
{vhost_access_query, {constant, true}}, {vhost_access_query, {constant, true}},
{resource_access_query, {constant, true}}, {resource_access_query, {constant, true}},
{topic_access_query, {constant, true}},
{tag_queries, [{administrator, {constant, false}}]}, {tag_queries, [{administrator, {constant, false}}]},
{use_ssl, false}, {use_ssl, false},
{use_starttls, false}, {use_starttls, false},

View File

@ -25,7 +25,7 @@
-behaviour(rabbit_authz_backend). -behaviour(rabbit_authz_backend).
-export([user_login_authentication/2, user_login_authorization/1, -export([user_login_authentication/2, user_login_authorization/1,
check_vhost_access/3, check_resource_access/3]). check_vhost_access/3, check_resource_access/3, check_topic_access/4]).
-export([get_connections/0]). -export([get_connections/0]).
@ -97,10 +97,27 @@ check_vhost_access(User = #auth_user{username = Username,
R. R.
check_resource_access(User = #auth_user{username = Username, check_resource_access(User = #auth_user{username = Username,
impl = #impl{user_dn = UserDN}}, impl = #impl{user_dn = UserDN}},
#resource{virtual_host = VHost, kind = topic = Resource, name = Name, options = Options}, #resource{virtual_host = VHost, kind = Type, name = Name},
Permission) -> Permission) ->
OptionsArgs = resource_options_as_variables(Options), Args = [{username, Username},
{user_dn, UserDN},
{vhost, VHost},
{resource, Type},
{name, Name},
{permission, Permission}],
?L("CHECK: ~s for ~s", [log_resource(Args), log_user(User)]),
R = evaluate_ldap(env(resource_access_query), Args, User),
?L("DECISION: ~s for ~s: ~p",
[log_resource(Args), log_user(User), log_result(R)]),
R.
check_topic_access(User = #auth_user{username = Username,
impl = #impl{user_dn = UserDN}},
#resource{virtual_host = VHost, kind = topic = Resource, name = Name},
Permission,
Context) ->
OptionsArgs = topic_context_as_options(Context),
Args = [{username, Username}, Args = [{username, Username},
{user_dn, UserDN}, {user_dn, UserDN},
{vhost, VHost}, {vhost, VHost},
@ -108,44 +125,21 @@ check_resource_access(User = #auth_user{username = Username,
{name, Name}, {name, Name},
{permission, Permission}] ++ OptionsArgs, {permission, Permission}] ++ OptionsArgs,
?L("CHECK: ~s for ~s", [log_resource(Args), log_user(User)]), ?L("CHECK: ~s for ~s", [log_resource(Args), log_user(User)]),
R = case evaluate_ldap(env(resource_access_query), Args, User) of R = evaluate_ldap(env(topic_access_query), Args, User),
{error, {for_query_incomplete}} ->
%% if there's no {resource, topic, ...} clause, let pass
true;
Result ->
Result
end,
?L("DECISION: ~s for ~s: ~p", ?L("DECISION: ~s for ~s: ~p",
[log_resource(Args), log_user(User), log_result(R)]), [log_resource(Args), log_user(User), log_result(R)]),
io:format("~p~n", [R]),
R;
check_resource_access(User = #auth_user{username = Username,
impl = #impl{user_dn = UserDN}},
#resource{virtual_host = VHost, kind = Type, name = Name, options = Options},
Permission) ->
OptionsArgs = resource_options_as_variables(Options),
Args = [{username, Username},
{user_dn, UserDN},
{vhost, VHost},
{resource, Type},
{name, Name},
{permission, Permission}] ++ OptionsArgs,
?L("CHECK: ~s for ~s", [log_resource(Args), log_user(User)]),
R = evaluate_ldap(env(resource_access_query), Args, User),
?L("DECISION: ~s for ~s: ~p",
[log_resource(Args), log_user(User), log_result(R)]),
R. R.
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
resource_options_as_variables(Options) when is_map(Options) -> topic_context_as_options(Context) when is_map(Context) ->
% filter options that would erase fixed variables % filter keys that would erase fixed variables
[{rabbit_data_coercion:to_atom(Key), maps:get(Key, Options)} [{rabbit_data_coercion:to_atom(Key), maps:get(Key, Context)}
|| Key <- maps:keys(Options), || Key <- maps:keys(Context),
lists:member( lists:member(
rabbit_data_coercion:to_atom(Key), rabbit_data_coercion:to_atom(Key),
?RESOURCE_ACCESS_QUERY_VARIABLES) =:= false]; ?RESOURCE_ACCESS_QUERY_VARIABLES) =:= false];
resource_options_as_variables(_) -> topic_context_as_options(_) ->
[]. [].
evaluate(Query, Args, User, LDAP) -> evaluate(Query, Args, User, LDAP) ->

View File

@ -366,48 +366,30 @@ topic_authorisation_ldap_only(Config) ->
ok = rabbit_ct_broker_helpers:rpc(Config, 0, ok = rabbit_ct_broker_helpers:rpc(Config, 0,
application, set_env, [rabbit, auth_backends, [rabbit_auth_backend_ldap]]), 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 %% let pass for topic
set_env(Config, [{resource_access_query, {for, [ set_env(Config, [{topic_access_query, {constant, true}}]),
{resource, exchange, {constant, true}},
{resource, queue, {constant, true}},
{resource, topic, {constant, true}}
]}}]),
P = #amqp_params_network{port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp)}, 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), test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok),
%% no {resource, topic, ...} clause, let pass
set_env(Config, [{resource_access_query, {for, [
{resource, exchange, {constant, true}},
{resource, queue, {constant, true}}
]}}]),
test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok),
%% check string substitution (on username) %% check string substitution (on username)
set_env(Config, [{resource_access_query, {for, [ set_env(Config, [{topic_access_query, {'and',
{resource, exchange, {constant, true}}, [{equals, "${username}", "Alice"}]
{resource, queue, {constant, true}}, }}]),
{resource, topic,
{'and',
[{equals, "${username}", "Alice"}]
}
}
]}}]),
test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok),
test_publish(P?BOB, <<"amq.topic">>, <<"a.b.c">>, fail), test_publish(P?BOB, <<"amq.topic">>, <<"a.b.c">>, fail),
%% check string substitution on routing key (with regex) %% check string substitution on routing key (with regex)
set_env(Config, [{resource_access_query, {for, [ set_env(Config, [{topic_access_query, {'and',
{resource, exchange, {constant, true}}, [{equals, "${username}", "Alice"},
{resource, queue, {constant, true}}, {match, {string, "${routing_key}"}, {string, "^a"}}
{resource, topic, ]
{'and', }}]),
[{equals, "${username}", "Alice"},
{match, {string, "${routing_key}"}, {string, "^a"}}
]
}
}
]}}]),
%% user and routing key OK %% user and routing key OK
test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok), test_publish(P?ALICE, <<"amq.topic">>, <<"a.b.c">>, ok),
%% user and routing key OK %% user and routing key OK