summaryrefslogtreecommitdiff
path: root/deps/rabbitmq_auth_backend_cache/src
diff options
context:
space:
mode:
Diffstat (limited to 'deps/rabbitmq_auth_backend_cache/src')
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl107
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache_app.erl37
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl35
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_dict.erl61
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets.erl71
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented.erl116
-rw-r--r--deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented_stateless.erl129
7 files changed, 556 insertions, 0 deletions
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl
new file mode 100644
index 0000000000..8a556723b5
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache.erl
@@ -0,0 +1,107 @@
+%% 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_auth_backend_cache).
+-include_lib("rabbit_common/include/rabbit.hrl").
+
+-behaviour(rabbit_authn_backend).
+-behaviour(rabbit_authz_backend).
+
+-export([user_login_authentication/2, user_login_authorization/2,
+ check_vhost_access/3, check_resource_access/4, check_topic_access/4,
+ state_can_expire/0]).
+
+%% API
+
+user_login_authentication(Username, AuthProps) ->
+ with_cache(authn, {user_login_authentication, [Username, AuthProps]},
+ fun({ok, _}) -> success;
+ ({refused, _, _}) -> refusal;
+ ({error, _} = Err) -> Err;
+ (_) -> unknown
+ end).
+
+user_login_authorization(Username, AuthProps) ->
+ with_cache(authz, {user_login_authorization, [Username, AuthProps]},
+ fun({ok, _}) -> success;
+ ({ok, _, _}) -> success;
+ ({refused, _, _}) -> refusal;
+ ({error, _} = Err) -> Err;
+ (_) -> unknown
+ end).
+
+check_vhost_access(#auth_user{} = AuthUser, VHostPath, AuthzData) ->
+ with_cache(authz, {check_vhost_access, [AuthUser, VHostPath, AuthzData]},
+ fun(true) -> success;
+ (false) -> refusal;
+ ({error, _} = Err) -> Err;
+ (_) -> unknown
+ end).
+
+check_resource_access(#auth_user{} = AuthUser,
+ #resource{} = Resource, Permission, AuthzContext) ->
+ with_cache(authz, {check_resource_access, [AuthUser, Resource, Permission, AuthzContext]},
+ fun(true) -> success;
+ (false) -> refusal;
+ ({error, _} = Err) -> Err;
+ (_) -> unknown
+ end).
+
+check_topic_access(#auth_user{} = AuthUser,
+ #resource{} = Resource, Permission, Context) ->
+ with_cache(authz, {check_topic_access, [AuthUser, Resource, Permission, Context]},
+ fun(true) -> success;
+ (false) -> refusal;
+ ({error, _} = Err) -> Err;
+ (_) -> unknown
+ end).
+
+state_can_expire() -> false.
+
+%%
+%% Implementation
+%%
+
+with_cache(BackendType, {F, A}, Fun) ->
+ {ok, AuthCache} = application:get_env(rabbitmq_auth_backend_cache,
+ cache_module),
+ case AuthCache:get({F, A}) of
+ {ok, Result} ->
+ Result;
+ {error, not_found} ->
+ Backend = get_cached_backend(BackendType),
+ {ok, TTL} = application:get_env(rabbitmq_auth_backend_cache,
+ cache_ttl),
+ BackendResult = apply(Backend, F, A),
+ case should_cache(BackendResult, Fun) of
+ true -> ok = AuthCache:put({F, A}, BackendResult, TTL);
+ false -> ok
+ end,
+ BackendResult
+ end.
+
+get_cached_backend(Type) ->
+ {ok, BackendConfig} = application:get_env(rabbitmq_auth_backend_cache,
+ cached_backend),
+ case BackendConfig of
+ Mod when is_atom(Mod) ->
+ Mod;
+ {N, Z} ->
+ case Type of
+ authn -> N;
+ authz -> Z
+ end
+ end.
+
+should_cache(Result, Fun) ->
+ {ok, CacheRefusals} = application:get_env(rabbitmq_auth_backend_cache,
+ cache_refusals),
+ case {Fun(Result), CacheRefusals} of
+ {success, _} -> true;
+ {refusal, true} -> true;
+ _ -> false
+ end.
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache_app.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache_app.erl
new file mode 100644
index 0000000000..c54f95393f
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_backend_cache_app.erl
@@ -0,0 +1,37 @@
+%% 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_auth_backend_cache_app).
+
+-behaviour(application).
+-export([start/2, stop/1]).
+
+-behaviour(supervisor).
+-export([init/1]).
+
+start(_Type, _StartArgs) ->
+ supervisor:start_link({local,?MODULE},?MODULE,[]).
+
+stop(_State) ->
+ ok.
+
+%%----------------------------------------------------------------------------
+
+init([]) ->
+ {ok, AuthCache} = application:get_env(rabbitmq_auth_backend_cache,
+ cache_module),
+
+ {ok, AuthCacheArgs} = application:get_env(rabbitmq_auth_backend_cache, cache_module_args),
+ % Load module to be able to check exported function.
+ code:load_file(AuthCache),
+ ChildSpecs = case erlang:function_exported(AuthCache, start_link,
+ length(AuthCacheArgs)) of
+ true -> [{auth_cache, {AuthCache, start_link, AuthCacheArgs},
+ permanent, 5000, worker, [AuthCache]}];
+ false -> []
+ end,
+ {ok, {{one_for_one,3,10}, ChildSpecs}}.
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl
new file mode 100644
index 0000000000..e1b7418d15
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache.erl
@@ -0,0 +1,35 @@
+%% 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_auth_cache).
+
+-export([expiration/1, expired/1]).
+
+-ifdef(use_specs).
+
+-callback get(term()) -> term().
+
+-callback put(term(), term(), integer()) -> ok.
+
+-callback delete(term()) -> ok.
+
+-else.
+
+-export([behaviour_info/1]).
+
+behaviour_info(callbacks) ->
+ [{get, 1}, {put, 3}, {delete, 1}];
+behaviour_info(_Other) ->
+ undefined.
+
+-endif.
+
+expiration(TTL) ->
+ erlang:system_time(milli_seconds) + TTL.
+
+expired(Exp) ->
+ erlang:system_time(milli_seconds) > Exp.
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_dict.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_dict.erl
new file mode 100644
index 0000000000..ce800a886e
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_dict.erl
@@ -0,0 +1,61 @@
+%% 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_auth_cache_dict).
+-behaviour(gen_server).
+-compile({no_auto_import,[get/1]}).
+-compile({no_auto_import,[put/2]}).
+
+-behaviour(rabbit_auth_cache).
+
+-export([start_link/0,
+ get/1, put/3, delete/1]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+get(Key) -> gen_server:call(?MODULE, {get, Key}).
+put(Key, Value, TTL) -> gen_server:cast(?MODULE, {put, Key, Value, TTL}).
+delete(Key) -> gen_server:call(?MODULE, {delete, Key}).
+
+init(_Args) -> {ok, nostate}.
+
+handle_call({get, Key}, _From, nostate) ->
+ Result = case erlang:get({items, Key}) of
+ undefined -> {error, not_found};
+ Val -> {ok, Val}
+ end,
+ {reply, Result, nostate};
+handle_call({delete, Key}, _From, nostate) ->
+ do_delete(Key),
+ {reply, ok, nostate}.
+
+handle_cast({put, Key, Value, TTL}, nostate) ->
+ erlang:put({items, Key}, Value),
+ {ok, TRef} = timer:apply_after(TTL, rabbit_auth_cache_dict, delete, [Key]),
+ erlang:put({timers, Key}, TRef),
+ {noreply, nostate}.
+
+handle_info(_Msg, nostate) ->
+ {noreply, nostate}.
+
+code_change(_OldVsn, nostate, _Extra) ->
+ {ok, nostate}.
+
+terminate(_Reason, nostate) ->
+ nostate.
+
+do_delete(Key) ->
+ erase({items, Key}),
+ case erlang:get({timers, Key}) of
+ undefined -> ok;
+ Tref -> timer:cancel(Tref),
+ erase({timers, Key})
+
+ end.
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets.erl
new file mode 100644
index 0000000000..4cd36c2b3a
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets.erl
@@ -0,0 +1,71 @@
+%% 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_auth_cache_ets).
+-behaviour(gen_server).
+-compile({no_auto_import,[get/1]}).
+-compile({no_auto_import,[put/2]}).
+
+-behaviour(rabbit_auth_cache).
+
+-export([start_link/0,
+ get/1, put/3, delete/1]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {cache, timers, ttl}).
+
+start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
+
+get(Key) -> gen_server:call(?MODULE, {get, Key}).
+put(Key, Value, TTL) ->
+ Expiration = rabbit_auth_cache:expiration(TTL),
+ gen_server:cast(?MODULE, {put, Key, Value, TTL, Expiration}).
+delete(Key) -> gen_server:call(?MODULE, {delete, Key}).
+
+init(_Args) ->
+ {ok, #state{cache = ets:new(?MODULE, [set, private]),
+ timers = ets:new(auth_cache_ets_timers, [set, private])}}.
+
+handle_call({get, Key}, _From, State = #state{cache = Table}) ->
+ Result = case ets:lookup(Table, Key) of
+ [{Key, {Exp, Val}}] -> case rabbit_auth_cache:expired(Exp) of
+ true -> {error, not_found};
+ false -> {ok, Val}
+ end;
+ [] -> {error, not_found}
+ end,
+ {reply, Result, State};
+handle_call({delete, Key}, _From, State = #state{cache = Table, timers = Timers}) ->
+ do_delete(Key, Table, Timers),
+ {reply, ok, State}.
+
+handle_cast({put, Key, Value, TTL, Expiration},
+ State = #state{cache = Table, timers = Timers}) ->
+ do_delete(Key, Table, Timers),
+ ets:insert(Table, {Key, {Expiration, Value}}),
+ {ok, TRef} = timer:apply_after(TTL, rabbit_auth_cache_ets, delete, [Key]),
+ ets:insert(Timers, {Key, TRef}),
+ {noreply, State}.
+
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, State = #state{}) ->
+ State.
+
+do_delete(Key, Table, Timers) ->
+ true = ets:delete(Table, Key),
+ case ets:lookup(Timers, Key) of
+ [{Key, Tref}] -> timer:cancel(Tref),
+ true = ets:delete(Timers, Key);
+ [] -> ok
+ end.
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented.erl
new file mode 100644
index 0000000000..cc7bcbfc02
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented.erl
@@ -0,0 +1,116 @@
+%% 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_auth_cache_ets_segmented).
+-behaviour(gen_server).
+-behaviour(rabbit_auth_cache).
+
+-export([start_link/1,
+ get/1, put/3, delete/1]).
+-export([gc/0]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-record(state, {
+ segments = [],
+ gc_timer,
+ segment_size}).
+
+start_link(SegmentSize) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [SegmentSize], []).
+
+get(Key) ->
+ case get_from_segments(Key) of
+ [] -> {error, not_found};
+ [V|_] -> {ok, V}
+ end.
+
+put(Key, Value, TTL) ->
+ Expiration = rabbit_auth_cache:expiration(TTL),
+ Segment = gen_server:call(?MODULE, {get_write_segment, Expiration}),
+ ets:insert(Segment, {Key, {Expiration, Value}}),
+ ok.
+
+delete(Key) ->
+ [ets:delete(Table, Key)
+ || Table <- gen_server:call(?MODULE, get_segment_tables)].
+
+gc() ->
+ case whereis(?MODULE) of
+ undefined -> ok;
+ Pid -> Pid ! gc
+ end.
+
+init([SegmentSize]) ->
+ InitSegment = ets:new(segment, [set, public]),
+ InitBoundary = rabbit_auth_cache:expiration(SegmentSize),
+ {ok, GCTimer} = timer:send_interval(SegmentSize * 2, gc),
+ {ok, #state{gc_timer = GCTimer, segment_size = SegmentSize,
+ segments = [{InitBoundary, InitSegment}]}}.
+
+handle_call({get_write_segment, Expiration}, _From,
+ State = #state{segments = Segments,
+ segment_size = SegmentSize}) ->
+ [{_, Segment} | _] = NewSegments = maybe_add_segment(Expiration, SegmentSize, Segments),
+ {reply, Segment, State#state{segments = NewSegments}};
+handle_call(get_segment_tables, _From, State = #state{segments = Segments}) ->
+ {_, Valid} = partition_expired_segments(Segments),
+ {_,Tables} = lists:unzip(Valid),
+ {reply, Tables, State}.
+
+handle_cast(_, State = #state{}) ->
+ {noreply, State}.
+
+handle_info(gc, State = #state{ segments = Segments }) ->
+ {Expired, Valid} = partition_expired_segments(Segments),
+ [ets:delete(Table) || {_, Table} <- Expired],
+ {noreply, State#state{ segments = Valid }};
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, State = #state{gc_timer = Timer}) ->
+ timer:cancel(Timer),
+ State.
+
+partition_expired_segments(Segments) ->
+ lists:partition(
+ fun({Boundary, _}) -> rabbit_auth_cache:expired(Boundary) end,
+ Segments).
+
+maybe_add_segment(Expiration, SegmentSize, OldSegments) ->
+ case OldSegments of
+ [{OldBoundary, _}|_] when OldBoundary > Expiration ->
+ OldSegments;
+ _ ->
+ NewBoundary = Expiration + SegmentSize,
+ Segment = ets:new(segment, [set, public]),
+ [{NewBoundary, Segment} | OldSegments]
+ end.
+
+get_from_segments(Key) ->
+ Tables = gen_server:call(?MODULE, get_segment_tables),
+ lists:flatmap(
+ fun(undefined) -> [];
+ (T) ->
+ try ets:lookup(T, Key) of
+ [{Key, {Exp, Val}}] ->
+ case rabbit_auth_cache:expired(Exp) of
+ true -> [];
+ false -> [Val]
+ end;
+ [] -> []
+ % ETS table can be deleted concurrently.
+ catch
+ error:badarg -> []
+ end
+ end,
+ Tables).
+
diff --git a/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented_stateless.erl b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented_stateless.erl
new file mode 100644
index 0000000000..fb959d2031
--- /dev/null
+++ b/deps/rabbitmq_auth_backend_cache/src/rabbit_auth_cache_ets_segmented_stateless.erl
@@ -0,0 +1,129 @@
+%% 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_auth_cache_ets_segmented_stateless).
+-behaviour(gen_server).
+-behaviour(rabbit_auth_cache).
+
+-export([start_link/1,
+ get/1, put/3, delete/1]).
+-export([gc/0]).
+
+-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
+ terminate/2, code_change/3]).
+
+-define(SEGMENT_TABLE, rabbit_auth_cache_ets_segmented_stateless_segment_table).
+
+-record(state, {gc_timer}).
+
+start_link(SegmentSize) ->
+ gen_server:start_link({local, ?MODULE}, ?MODULE, [SegmentSize], []).
+
+get(Key) ->
+ case get_from_segments(Key) of
+ [] -> {error, not_found};
+ [V|_] -> {ok, V}
+ end.
+
+put(Key, Value, TTL) ->
+ Expiration = rabbit_auth_cache:expiration(TTL),
+ [{_, SegmentSize}] = ets:lookup(?SEGMENT_TABLE, segment_size),
+ Segment = segment(Expiration, SegmentSize),
+ Table = case ets:lookup(?SEGMENT_TABLE, Segment) of
+ [{Segment, T}] -> T;
+ [] -> add_segment(Segment)
+ end,
+ ets:insert(Table, {Key, {Expiration, Value}}),
+ ok.
+
+delete(Key) ->
+ [ets:delete(Table, Key)
+ || Table <- get_all_segment_tables()].
+
+gc() ->
+ case whereis(?MODULE) of
+ undefined -> ok;
+ Pid -> Pid ! gc
+ end.
+
+init([SegmentSize]) ->
+ ets:new(?SEGMENT_TABLE, [ordered_set, named_table, public]),
+ ets:insert(?SEGMENT_TABLE, {segment_size, SegmentSize}),
+
+ InitSegment = segment(rabbit_auth_cache:expiration(SegmentSize), SegmentSize),
+ do_add_segment(InitSegment),
+
+ {ok, GCTimer} = timer:send_interval(SegmentSize * 2, gc),
+ {ok, #state{gc_timer = GCTimer}}.
+
+handle_call({add_segment, Segment}, _From, State) ->
+ %% Double check segment if it's already created
+ Table = do_add_segment(Segment),
+ {reply, Table, State}.
+
+handle_cast(_, State = #state{}) ->
+ {noreply, State}.
+
+handle_info(gc, State = #state{}) ->
+ Now = erlang:system_time(milli_seconds),
+ MatchSpec = [{{'$1', '$2'}, [{'<', '$1', {const, Now}}], ['$2']}],
+ Expired = ets:select(?SEGMENT_TABLE, MatchSpec),
+ [ets:delete(Table) || Table <- Expired],
+ {noreply, State};
+handle_info(_Msg, State) ->
+ {noreply, State}.
+
+code_change(_OldVsn, State, _Extra) ->
+ {ok, State}.
+
+terminate(_Reason, State = #state{gc_timer = Timer}) ->
+ timer:cancel(Timer),
+ State.
+
+segment(Expiration, SegmentSize) ->
+ Begin = ((Expiration div SegmentSize) * SegmentSize),
+ End = Begin + SegmentSize,
+ End.
+
+add_segment(Segment) ->
+ gen_server:call(?MODULE, {add_segment, Segment}).
+
+do_add_segment(Segment) ->
+ case ets:lookup(?SEGMENT_TABLE, Segment) of
+ [{Segment, Table}] -> Table;
+ [] -> Table = ets:new(segment, [set, public]),
+ ets:insert(?SEGMENT_TABLE, {Segment, Table}),
+ Table
+ end.
+
+get_segment_tables() ->
+ Now = erlang:system_time(milli_seconds),
+ MatchSpec = [{{'$1', '$2'}, [{'>', '$1', {const, Now}}], ['$_']}],
+ [V || {K, V} <- ets:select(?SEGMENT_TABLE, MatchSpec), K =/= segment_size].
+
+get_all_segment_tables() ->
+ [V || {K, V} <- ets:tab2list(?SEGMENT_TABLE), K =/= segment_size].
+
+get_from_segments(Key) ->
+ Tables = get_segment_tables(),
+ lists:flatmap(
+ fun(undefined) -> [];
+ (T) ->
+ try ets:lookup(T, Key) of
+ [{Key, {Exp, Val}}] ->
+ case rabbit_auth_cache:expired(Exp) of
+ true -> [];
+ false -> [Val]
+ end;
+ [] -> []
+ % ETS table can be deleted concurrently.
+ catch
+ error:badarg -> []
+ end
+ end,
+ Tables).
+