summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon MacMullen <simon@rabbitmq.com>2014-11-14 17:41:03 +0000
committerSimon MacMullen <simon@rabbitmq.com>2014-11-14 17:41:03 +0000
commit4f241346ab6e8b24546ff75298ad6fc99b946e01 (patch)
treedf002ab0cee79355b05a75ab1973075b6585ffc9
parent54f8c42b87e4131ec02e9103629b59147b14bbd2 (diff)
parent8a6ad3517e031b8b7b85c63ab24d062d1d647b5b (diff)
downloadrabbitmq-server-4f241346ab6e8b24546ff75298ad6fc99b946e01.tar.gz
Merge bug26469
-rw-r--r--include/rabbit.hrl11
-rw-r--r--src/rabbit_access_control.erl109
-rw-r--r--src/rabbit_auth_backend_dummy.erl25
-rw-r--r--src/rabbit_auth_backend_internal.erl34
-rw-r--r--src/rabbit_authn_backend.erl49
-rw-r--r--src/rabbit_authz_backend.erl (renamed from src/rabbit_auth_backend.erl)40
-rw-r--r--src/rabbit_channel.erl3
-rw-r--r--src/rabbit_direct.erl2
-rw-r--r--src/rabbit_reader.erl2
-rw-r--r--src/rabbit_types.erl14
-rw-r--r--test/src/rabbit_tests.erl8
11 files changed, 201 insertions, 96 deletions
diff --git a/include/rabbit.hrl b/include/rabbit.hrl
index 74e165cd..9cbd978e 100644
--- a/include/rabbit.hrl
+++ b/include/rabbit.hrl
@@ -14,12 +14,17 @@
%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.
%%
+%% Passed around most places
-record(user, {username,
tags,
- auth_backend, %% Module this user came from
- impl %% Scratch space for that module
- }).
+ authz_backends}). %% List of {Module, AuthUserImpl} pairs
+%% Passed to auth backends
+-record(auth_user, {username,
+ tags,
+ impl}).
+
+%% Implementation for the internal auth backend
-record(internal_user, {username, password_hash, tags}).
-record(permission, {configure, write, read}).
-record(user_vhost, {username, virtual_host}).
diff --git a/src/rabbit_access_control.erl b/src/rabbit_access_control.erl
index b0a9c0d8..d1577432 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/2, check_resource_access/3]).
+ check_vhost_access/3, check_resource_access/3]).
%%----------------------------------------------------------------------------
@@ -38,8 +38,8 @@
-spec(check_user_loopback/2 :: (rabbit_types:username(),
rabbit_net:socket() | inet:ip_address())
-> 'ok' | 'not_allowed').
--spec(check_vhost_access/2 ::
- (rabbit_types:user(), rabbit_types:vhost())
+-spec(check_vhost_access/3 ::
+ (rabbit_types:user(), rabbit_types:vhost(), rabbit_net:socket())
-> 'ok' | rabbit_types:channel_exit()).
-spec(check_resource_access/3 ::
(rabbit_types:user(), rabbit_types:r(atom()), permission_atom())
@@ -55,19 +55,29 @@ check_user_pass_login(Username, Password) ->
check_user_login(Username, AuthProps) ->
{ok, Modules} = application:get_env(rabbit, auth_backends),
R = lists:foldl(
- fun ({ModN, ModZ}, {refused, _, _}) ->
+ fun ({ModN, ModZs0}, {refused, _, _}) ->
+ ModZs = case ModZs0 of
+ A when is_atom(A) -> [A];
+ L when is_list(L) -> L
+ end,
%% Different modules for authN vs authZ. So authenticate
%% with authN module, then if that succeeds do
- %% passwordless (i.e pre-authenticated) login with authZ
- %% module, and use the #user{} the latter gives us.
- case try_login(ModN, Username, AuthProps) of
- {ok, _} -> try_login(ModZ, Username, []);
- Else -> Else
+ %% passwordless (i.e pre-authenticated) login with authZ.
+ case try_authenticate(ModN, Username, AuthProps) of
+ {ok, ModNUser = #auth_user{username = Username2}} ->
+ user(ModNUser, try_authorize(ModZs, Username2));
+ Else ->
+ Else
end;
(Mod, {refused, _, _}) ->
%% Same module for authN and authZ. Just take the result
%% it gives us
- try_login(Mod, Username, AuthProps);
+ case try_authenticate(Mod, Username, AuthProps) of
+ {ok, ModNUser = #auth_user{impl = Impl}} ->
+ user(ModNUser, {ok, [{Mod, Impl}]});
+ Else ->
+ Else
+ end;
(_, {ok, User}) ->
%% We've successfully authenticated. Skip to the end...
{ok, User}
@@ -78,13 +88,39 @@ check_user_login(Username, AuthProps) ->
end, [{name, Username}]),
R.
-try_login(Module, Username, AuthProps) ->
- case Module:check_user_login(Username, AuthProps) of
- {error, E} -> {refused, "~s failed authenticating ~s: ~p~n",
- [Module, Username, E]};
- Else -> Else
+try_authenticate(Module, Username, AuthProps) ->
+ case Module:user_login_authentication(Username, AuthProps) of
+ {ok, AuthUser} -> {ok, AuthUser};
+ {error, E} -> {refused, "~s failed authenticating ~s: ~p~n",
+ [Module, Username, E]};
+ {refused, F, A} -> {refused, F, A}
end.
+try_authorize(Modules, Username) ->
+ lists:foldr(
+ fun (Module, {ok, ModsImpls}) ->
+ case Module:user_login_authorization(Username) of
+ {ok, Impl} -> {ok, [{Module, Impl} | ModsImpls]};
+ {error, E} -> {refused, "~s failed authorizing ~s: ~p~n",
+ [Module, Username, E]};
+ {refused, F, A} -> {refused, F, A}
+ end;
+ (_, {refused, _, _} = Error) ->
+ Error
+ end, {ok, []}, Modules).
+
+user(#auth_user{username = Username, tags = Tags}, {ok, ModZImpls}) ->
+ {ok, #user{username = Username,
+ tags = Tags,
+ authz_backends = ModZImpls}};
+user(_AuthUser, Error) ->
+ Error.
+
+auth_user(#user{username = Username, tags = Tags}, Impl) ->
+ #auth_user{username = Username,
+ tags = Tags,
+ impl = Impl}.
+
check_user_loopback(Username, SockOrAddr) ->
{ok, Users} = application:get_env(rabbit, loopback_users),
case rabbit_net:is_loopback(SockOrAddr)
@@ -93,29 +129,38 @@ check_user_loopback(Username, SockOrAddr) ->
false -> not_allowed
end.
-check_vhost_access(User = #user{ username = Username,
- auth_backend = Module }, VHostPath) ->
- check_access(
- fun() ->
- %% TODO this could be an andalso shortcut under >R13A
- case rabbit_vhost:exists(VHostPath) of
- false -> false;
- true -> Module:check_vhost_access(User, VHostPath)
- end
- end,
- Module, "access to vhost '~s' refused for user '~s'",
- [VHostPath, Username]).
+check_vhost_access(User = #user{username = Username,
+ authz_backends = Modules}, VHostPath, Sock) ->
+ lists:foldl(
+ fun({Mod, Impl}, ok) ->
+ check_access(
+ fun() ->
+ rabbit_vhost:exists(VHostPath) andalso
+ Mod:check_vhost_access(
+ auth_user(User, Impl), VHostPath, Sock)
+ end,
+ Mod, "access to vhost '~s' refused for user '~s'",
+ [VHostPath, Username]);
+ (_, Else) ->
+ Else
+ end, ok, Modules).
check_resource_access(User, R = #resource{kind = exchange, name = <<"">>},
Permission) ->
check_resource_access(User, R#resource{name = <<"amq.default">>},
Permission);
-check_resource_access(User = #user{username = Username, auth_backend = Module},
+check_resource_access(User = #user{username = Username,
+ authz_backends = Modules},
Resource, Permission) ->
- check_access(
- fun() -> Module:check_resource_access(User, Resource, Permission) end,
- Module, "access to ~s refused for user '~s'",
- [rabbit_misc:rs(Resource), Username]).
+ lists:foldl(
+ fun({Module, Impl}, ok) ->
+ check_access(
+ fun() -> Module:check_resource_access(
+ auth_user(User, Impl), Resource, Permission) end,
+ Module, "access to ~s refused for user '~s'",
+ [rabbit_misc:rs(Resource), Username]);
+ (_, Else) -> Else
+ end, ok, Modules).
check_access(Fun, Module, ErrStr, ErrArgs) ->
Allow = case Fun() of
diff --git a/src/rabbit_auth_backend_dummy.erl b/src/rabbit_auth_backend_dummy.erl
index 5daca368..d2f07c1d 100644
--- a/src/rabbit_auth_backend_dummy.erl
+++ b/src/rabbit_auth_backend_dummy.erl
@@ -17,11 +17,12 @@
-module(rabbit_auth_backend_dummy).
-include("rabbit.hrl").
--behaviour(rabbit_auth_backend).
+-behaviour(rabbit_authn_backend).
+-behaviour(rabbit_authz_backend).
--export([description/0]).
-export([user/0]).
--export([check_user_login/2, check_vhost_access/2, check_resource_access/3]).
+-export([user_login_authentication/2, user_login_authorization/1,
+ check_vhost_access/3, check_resource_access/3]).
-ifdef(use_specs).
@@ -31,19 +32,17 @@
%% A user to be used by the direct client when permission checks are
%% not needed. This user can do anything AMQPish.
-user() -> #user{username = <<"none">>,
- tags = [],
- auth_backend = ?MODULE,
- impl = none}.
+user() -> #user{username = <<"none">>,
+ tags = [],
+ authz_backends = [{?MODULE, none}]}.
%% Implementation of rabbit_auth_backend
-description() ->
- [{name, <<"Dummy">>},
- {description, <<"Database for the dummy user">>}].
+user_login_authentication(_, _) ->
+ {refused, "cannot log in conventionally as dummy user", []}.
-check_user_login(_, _) ->
+user_login_authorization(_) ->
{refused, "cannot log in conventionally as dummy user", []}.
-check_vhost_access(#user{}, _VHostPath) -> true.
-check_resource_access(#user{}, #resource{}, _Permission) -> true.
+check_vhost_access(#auth_user{}, _VHostPath, _Sock) -> true.
+check_resource_access(#auth_user{}, #resource{}, _Permission) -> true.
diff --git a/src/rabbit_auth_backend_internal.erl b/src/rabbit_auth_backend_internal.erl
index fd1c4e8e..20a5766d 100644
--- a/src/rabbit_auth_backend_internal.erl
+++ b/src/rabbit_auth_backend_internal.erl
@@ -17,10 +17,11 @@
-module(rabbit_auth_backend_internal).
-include("rabbit.hrl").
--behaviour(rabbit_auth_backend).
+-behaviour(rabbit_authn_backend).
+-behaviour(rabbit_authz_backend).
--export([description/0]).
--export([check_user_login/2, check_vhost_access/2, check_resource_access/3]).
+-export([user_login_authentication/2, user_login_authorization/1,
+ check_vhost_access/3, check_resource_access/3]).
-export([add_user/2, delete_user/1, lookup_user/1,
change_password/2, clear_password/1,
@@ -76,13 +77,9 @@
%%----------------------------------------------------------------------------
%% Implementation of rabbit_auth_backend
-description() ->
- [{name, <<"Internal">>},
- {description, <<"Internal user / password database">>}].
-
-check_user_login(Username, []) ->
+user_login_authentication(Username, []) ->
internal_check_user_login(Username, fun(_) -> true end);
-check_user_login(Username, [{password, Cleartext}]) ->
+user_login_authentication(Username, [{password, Cleartext}]) ->
internal_check_user_login(
Username,
fun (#internal_user{password_hash = <<Salt:4/binary, Hash/binary>>}) ->
@@ -90,25 +87,30 @@ check_user_login(Username, [{password, Cleartext}]) ->
(#internal_user{}) ->
false
end);
-check_user_login(Username, AuthProps) ->
+user_login_authentication(Username, AuthProps) ->
exit({unknown_auth_props, Username, AuthProps}).
+user_login_authorization(Username) ->
+ case user_login_authentication(Username, []) of
+ {ok, #auth_user{impl = Impl}} -> {ok, Impl};
+ Else -> Else
+ end.
+
internal_check_user_login(Username, Fun) ->
Refused = {refused, "user '~s' - invalid credentials", [Username]},
case lookup_user(Username) of
{ok, User = #internal_user{tags = Tags}} ->
case Fun(User) of
- true -> {ok, #user{username = Username,
- tags = Tags,
- auth_backend = ?MODULE,
- impl = User}};
+ true -> {ok, #auth_user{username = Username,
+ tags = Tags,
+ impl = none}};
_ -> Refused
end;
{error, not_found} ->
Refused
end.
-check_vhost_access(#user{username = Username}, VHostPath) ->
+check_vhost_access(#auth_user{username = Username}, VHostPath, _Sock) ->
case mnesia:dirty_read({rabbit_user_permission,
#user_vhost{username = Username,
virtual_host = VHostPath}}) of
@@ -116,7 +118,7 @@ check_vhost_access(#user{username = Username}, VHostPath) ->
[_R] -> true
end.
-check_resource_access(#user{username = Username},
+check_resource_access(#auth_user{username = Username},
#resource{virtual_host = VHostPath, name = Name},
Permission) ->
case mnesia:dirty_read({rabbit_user_permission,
diff --git a/src/rabbit_authn_backend.erl b/src/rabbit_authn_backend.erl
new file mode 100644
index 00000000..cfc3f5db
--- /dev/null
+++ b/src/rabbit_authn_backend.erl
@@ -0,0 +1,49 @@
+%% 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) 2007-2014 GoPivotal, Inc. All rights reserved.
+%%
+
+-module(rabbit_authn_backend).
+
+-include("rabbit.hrl").
+
+-ifdef(use_specs).
+
+%% Check a user can log in, given a username and a proplist of
+%% authentication information (e.g. [{password, Password}]). If your
+%% backend is not to be used for authentication, this should always
+%% refuse access.
+%%
+%% Possible responses:
+%% {ok, User}
+%% Authentication succeeded, and here's the user record.
+%% {error, Error}
+%% Something went wrong. Log and die.
+%% {refused, Msg, Args}
+%% Client failed authentication. Log and die.
+-callback user_login_authentication(rabbit_types:username(), [term()]) ->
+ {'ok', rabbit_types:auth_user()} |
+ {'refused', string(), [any()]} |
+ {'error', any()}.
+
+-else.
+
+-export([behaviour_info/1]).
+
+behaviour_info(callbacks) ->
+ [{user_login_authentication, 2}];
+behaviour_info(_Other) ->
+ undefined.
+
+-endif.
diff --git a/src/rabbit_auth_backend.erl b/src/rabbit_authz_backend.erl
index a7dd6494..ff5f014e 100644
--- a/src/rabbit_auth_backend.erl
+++ b/src/rabbit_authz_backend.erl
@@ -14,47 +14,49 @@
%% Copyright (c) 2007-2014 GoPivotal, Inc. All rights reserved.
%%
--module(rabbit_auth_backend).
+-module(rabbit_authz_backend).
--ifdef(use_specs).
+-include("rabbit.hrl").
-%% A description proplist as with auth mechanisms,
-%% exchanges. Currently unused.
--callback description() -> [proplists:property()].
+-ifdef(use_specs).
-%% Check a user can log in, given a username and a proplist of
-%% authentication information (e.g. [{password, Password}]).
+%% Check a user can log in, when this backend is being used for
+%% authorisation only. Authentication has already taken place
+%% successfully, but we need to check that the user exists in this
+%% backend, and initialise any impl field we will want to have passed
+%% back in future calls to check_vhost_access/3 and
+%% check_resource_access/3.
%%
%% Possible responses:
-%% {ok, User}
-%% Authentication succeeded, and here's the user record.
+%% {ok, Impl}
+%% User authorisation succeeded, and here's the impl field.
%% {error, Error}
%% Something went wrong. Log and die.
%% {refused, Msg, Args}
-%% Client failed authentication. Log and die.
--callback check_user_login(rabbit_types:username(), [term()]) ->
- {'ok', rabbit_types:user()} |
+%% User authorisation failed. Log and die.
+-callback user_login_authorization(rabbit_types:username()) ->
+ {'ok', any()} |
{'refused', string(), [any()]} |
{'error', any()}.
-%% Given #user and vhost, can a user log in to a vhost?
+%% Given #auth_user and vhost, can a user log in to a vhost?
%% Possible responses:
%% true
%% false
%% {error, Error}
%% Something went wrong. Log and die.
--callback check_vhost_access(rabbit_types:user(), rabbit_types:vhost()) ->
+-callback check_vhost_access(rabbit_types:auth_user(),
+ rabbit_types:vhost(), rabbit_net:socket()) ->
boolean() | {'error', any()}.
-
-%% Given #user, resource and permission, can a user access a resource?
+%% Given #auth_user, resource and permission, can a user access a resource?
%%
%% Possible responses:
%% true
%% false
%% {error, Error}
%% Something went wrong. Log and die.
--callback check_resource_access(rabbit_types:user(),
+-callback check_resource_access(rabbit_types:auth_user(),
rabbit_types:r(atom()),
rabbit_access_control:permission_atom()) ->
boolean() | {'error', any()}.
@@ -64,8 +66,8 @@
-export([behaviour_info/1]).
behaviour_info(callbacks) ->
- [{description, 0}, {check_user_login, 2}, {check_vhost_access, 2},
- {check_resource_access, 3}];
+ [{user_login_authorization, 1},
+ {check_vhost_access, 3}, {check_resource_access, 3}];
behaviour_info(_Other) ->
undefined.
diff --git a/src/rabbit_channel.erl b/src/rabbit_channel.erl
index 8632e1b3..13cc925c 100644
--- a/src/rabbit_channel.erl
+++ b/src/rabbit_channel.erl
@@ -581,7 +581,8 @@ check_user_id_header(#'P_basic'{user_id = Username},
#ch{user = #user{username = Username}}) ->
ok;
check_user_id_header(
- #'P_basic'{}, #ch{user = #user{auth_backend = rabbit_auth_backend_dummy}}) ->
+ #'P_basic'{}, #ch{user = #user{authz_backends =
+ [{rabbit_auth_backend_dummy, _}]}}) ->
ok;
check_user_id_header(#'P_basic'{user_id = Claimed},
#ch{user = #user{username = Actual,
diff --git a/src/rabbit_direct.erl b/src/rabbit_direct.erl
index 749a67b1..f6140f09 100644
--- a/src/rabbit_direct.erl
+++ b/src/rabbit_direct.erl
@@ -92,7 +92,7 @@ connect0(AuthFun, VHost, Protocol, Pid, Infos) ->
end.
connect1(User, VHost, Protocol, Pid, Infos) ->
- try rabbit_access_control:check_vhost_access(User, VHost) of
+ try rabbit_access_control:check_vhost_access(User, VHost, undefined) of
ok -> ok = pg_local:join(rabbit_direct, Pid),
rabbit_event:notify(connection_created, Infos),
{ok, {User, rabbit_reader:server_properties(Protocol)}}
diff --git a/src/rabbit_reader.erl b/src/rabbit_reader.erl
index ca73006a..2033dd14 100644
--- a/src/rabbit_reader.erl
+++ b/src/rabbit_reader.erl
@@ -944,7 +944,7 @@ handle_method0(#'connection.open'{virtual_host = VHostPath},
helper_sup = SupPid,
sock = Sock,
throttle = Throttle}) ->
- ok = rabbit_access_control:check_vhost_access(User, VHostPath),
+ ok = rabbit_access_control:check_vhost_access(User, VHostPath, Sock),
NewConnection = Connection#connection{vhost = VHostPath},
ok = send_on_channel0(Sock, #'connection.open_ok'{}, Protocol),
Conserve = rabbit_alarm:register(self(), {?MODULE, conserve_resources, []}),
diff --git a/src/rabbit_types.erl b/src/rabbit_types.erl
index ba48867a..039568df 100644
--- a/src/rabbit_types.erl
+++ b/src/rabbit_types.erl
@@ -27,7 +27,7 @@
vhost/0, ctag/0, amqp_error/0, r/1, r2/2, r3/3, listener/0,
binding/0, binding_source/0, binding_destination/0,
amqqueue/0, exchange/0,
- connection/0, protocol/0, user/0, internal_user/0,
+ connection/0, protocol/0, auth_user/0, user/0, internal_user/0,
username/0, password/0, password_hash/0,
ok/1, error/1, ok_or_error/1, ok_or_error2/2, ok_pid_or_error/0,
channel_exit/0, connection_exit/0, mfargs/0, proc_name/0,
@@ -131,11 +131,15 @@
-type(protocol() :: rabbit_framing:protocol()).
+-type(auth_user() ::
+ #auth_user{username :: username(),
+ tags :: [atom()],
+ impl :: any()}).
+
-type(user() ::
- #user{username :: username(),
- tags :: [atom()],
- auth_backend :: atom(),
- impl :: any()}).
+ #user{username :: username(),
+ tags :: [atom()],
+ authz_backends :: [{atom(), any()}]}).
-type(internal_user() ::
#internal_user{username :: username(),
diff --git a/test/src/rabbit_tests.erl b/test/src/rabbit_tests.erl
index ef6b756b..dcbec8f6 100644
--- a/test/src/rabbit_tests.erl
+++ b/test/src/rabbit_tests.erl
@@ -1292,11 +1292,9 @@ test_spawn_remote() ->
end.
user(Username) ->
- #user{username = Username,
- tags = [administrator],
- auth_backend = rabbit_auth_backend_internal,
- impl = #internal_user{username = Username,
- tags = [administrator]}}.
+ #user{username = Username,
+ tags = [administrator],
+ authz_backends = [{rabbit_auth_backend_internal, none}]}.
test_confirms() ->
{_Writer, Ch} = test_spawn(),