diff options
Diffstat (limited to 'deps/rabbitmq_auth_backend_cache/src')
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). + |