Skip to content

Commit

Permalink
Merge pull request rabbitmq#7433 from rabbitmq/amazon-mq-preset-users
Browse files Browse the repository at this point in the history
Add users on vhost creation per default_users config (with naming changes)
  • Loading branch information
michaelklishin authored Feb 26, 2023
2 parents 1905995 + 5e9e2f2 commit 659f16b
Show file tree
Hide file tree
Showing 5 changed files with 288 additions and 106 deletions.
105 changes: 56 additions & 49 deletions deps/rabbit/priv/schema/rabbit.schema
Original file line number Diff line number Diff line change
Expand Up @@ -616,8 +616,6 @@ end}.
{datatype, string}
]}.



%%
%% Default User / VHost
%% ====================
Expand Down Expand Up @@ -681,64 +679,91 @@ fun(Conf) ->
[list_to_binary(Configure), list_to_binary(Read), list_to_binary(Write)]
end}.

%%
%% Extra Default Users
%% ====================
%%

{mapping, "default_users.$name.vhost_pattern", "rabbit.default_users", [
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_users.$name.password", "rabbit.default_users", [
{datatype, string}
]}.

{mapping, "default_users.$name.configure", "rabbit.default_users", [
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_users.$name.read", "rabbit.default_users", [
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_users.$name.write", "rabbit.default_users", [
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_users.$name.tags", "rabbit.default_users", [
{datatype, {list, atom}}
]}.

{translation, "rabbit.default_users", fun(Conf) ->
case rabbit_cuttlefish:aggregate_props(Conf, ["default_users"]) of
[] -> cuttlefish:unset();
Props -> Props
end
end}.

%%
%% Default Policies
%% ====================
%%

{mapping, "default_policies.operator.$id.vhost_pattern", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, ".*"},
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_policies.operator.$id.queue_pattern", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, ".*"},
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_policies.operator.$id.expires", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, "1s"},
{datatype, {duration, ms}}
]}.

{mapping, "default_policies.operator.$id.message_ttl", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, "1s"},
{datatype, {duration, ms}}
]}.

{mapping, "default_policies.operator.$id.max_length", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, 100},
{validators, ["non_zero_positive_integer"]},
{datatype, integer}
]}.

{mapping, "default_policies.operator.$id.max_length_bytes", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, "1GB"},
{validators, ["non_zero_positive_integer"]},
{datatype, bytesize}
]}.

{mapping, "default_policies.operator.$id.max_in_memory_bytes", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, "1GB"},
{validators, ["non_zero_positive_integer"]},
{datatype, bytesize}
]}.

{mapping, "default_policies.operator.$id.max_in_memory_length", "rabbit.default_policies.operator",
[
{include_default, 1},
{commented, 1000},
{validators, ["non_zero_positive_integer"]},
{datatype, integer}
]}.

{mapping, "default_policies.operator.$id.delivery_limit", "rabbit.default_policies.operator", [
{include_default, 1},
{commented, 1},
{validators, ["non_zero_positive_integer"]},
{datatype, integer}
]}.
Expand All @@ -759,55 +784,37 @@ end}.
{["default_policies","operator",ID|T],V};
(E) -> E
end),
Props1 = lists:map(
fun({K, Ss}) ->
{K,
lists:map(fun({N, V}) ->
{binary:replace(N, <<"_">>, <<"-">>, [global]), V}
end, Ss)}
end, Props),
case Props1 of
case Props of
[] -> cuttlefish:unset();
_ -> Props1
end,
Props1
Props -> Props
end
end}.

%%
%% Default VHost Limits
%% ====================
%%

{mapping, "default_limits.vhosts.$id.pattern", "rabbit.default_limits.vhosts", [
{include_default, 1},
{commented, ".*"},
{validators, ["valid_regex"]},
{datatype, string}
]}.

{mapping, "default_limits.vhosts.$id.max_connections", "rabbit.default_limits.vhosts", [
{include_default, 1},
{commented, 1000},
{validators, [ "non_zero_positive_integer"]},
{datatype, integer}
]}.

{mapping, "default_limits.vhosts.$id.max_queues", "rabbit.default_limits.vhosts", [
{include_default, 1},
{commented, 100},
{validators, [ "non_zero_positive_integer"]},
{datatype, integer}
]}.

{translation, "rabbit.default_limits.vhosts", fun(Conf) ->
Props = rabbit_cuttlefish:aggregate_props(Conf, ["default_limits", "vhosts"]),
Props1 = lists:map(
fun({K, Ss}) ->
{K,
lists:map(fun({N, V}) ->
{binary:replace(N, <<"_">>, <<"-">>, [global]), V}
end, Ss)}
end, Props),
case Props1 of
case rabbit_cuttlefish:aggregate_props(Conf, ["default_limits", "vhosts"]) of
[] -> cuttlefish:unset();
_ -> Props1
end,
Props1
Props -> Props
end
end}.

%% Tags for default user
Expand Down
169 changes: 169 additions & 0 deletions deps/rabbit/src/rabbit_db_vhost_defaults.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
%% 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) 2023-2023 VMware, Inc. or its affiliates. All rights reserved.
%%

-module(rabbit_db_vhost_defaults).

-export([apply/2]).
-export([list_limits/1, list_operator_policies/1, list_users/1]).

-type definitions() :: [{binary(), term()}].

-record(seeding_policy, {
name :: binary(),
queue_pattern = <<".*">> :: binary(),
definition = [] :: definitions()
}).

-type seeded_user_properties() :: #{
name := binary(),
configure := binary(),
read := binary(),
write := binary(),
password := binary(),
tags := [atom()],
_ => _
}.

%% Apply all matching defaults to a VHost.
-spec apply(vhost:name(), rabbit_types:username()) -> ok.
apply(VHost, ActingUser) ->
case list_limits(VHost) of
[] ->
ok;
L ->
ok = rabbit_vhost_limit:set(VHost, L, ActingUser),
rabbit_log:info("Applied default limits to vhost '~tp': ~tp", [VHost, L])
end,
lists:foreach(
fun(P) ->
ok = rabbit_policy:set_op(VHost, P#seeding_policy.name, P#seeding_policy.queue_pattern, P#seeding_policy.definition,
undefined, undefined, ActingUser),
rabbit_log:info("Applied default operator policy to vhost '~tp': ~tp", [VHost, P])
end,
list_operator_policies(VHost)
),
lists:foreach(
fun(U) ->
ok = add_user(VHost, U, ActingUser),
rabbit_log:info("Added default user to vhost '~tp': ~tp", [VHost, maps:remove(password, U)])
end,
list_users(VHost)
),
ok.

%%
%% Helpers
%%

%% Limits that were configured with a matching vhost pattern.
-spec list_limits(vhost:name()) -> proplists:proplist().
list_limits(VHost) ->
AllLimits = application:get_env(rabbit, default_limits, []),
VHostLimits = proplists:get_value(vhosts, AllLimits, []),
Match = lists:search(
fun({_, Ss}) ->
RE = proplists:get_value(<<"pattern">>, Ss, ".*"),
re:run(VHost, RE, [{capture, none}]) =:= match
end,
VHostLimits
),
case Match of
{value, {_, Ss}} ->
Ss1 = proplists:delete(<<"pattern">>, Ss),
underscore_to_dash(Ss1);
_ ->
[]
end.

%% Operator policies that were configured with a matching vhost pattern.
-spec list_operator_policies(vhost:name()) -> [#seeding_policy{}].
list_operator_policies(VHost) ->
AllPolicies = application:get_env(rabbit, default_policies, []),
OpPolicies = proplists:get_value(operator, AllPolicies, []),
lists:filtermap(
fun({PolicyName, Ss}) ->
RE = proplists:get_value(<<"vhost_pattern">>, Ss, ".*"),
case re:run(VHost, RE, [{capture, none}]) of
match ->
QPattern = proplists:get_value(<<"queue_pattern">>, Ss, <<".*">>),
Ss1 = proplists:delete(<<"queue_pattern">>, Ss),
Ss2 = proplists:delete(<<"vhost_pattern">>, Ss1),
{true, #seeding_policy{
name = PolicyName,
queue_pattern = QPattern,
definition = underscore_to_dash(Ss2)
}};
_ ->
false
end
end,
OpPolicies
).

%% Users (permissions) that were configured with a matching vhost pattern.
-spec list_users(vhost:name()) -> [seeded_user_properties()].
list_users(VHost) ->
Users = application:get_env(rabbit, default_users, []),
lists:filtermap(
fun({Username, Ss}) ->
RE = proplists:get_value(<<"vhost_pattern">>, Ss, ".*"),
case re:run(VHost, RE, [{capture, none}]) of
match ->
C = rabbit_data_coercion:to_binary(
proplists:get_value(<<"configure">>, Ss, <<".*">>)
),
R = rabbit_data_coercion:to_binary(
proplists:get_value(<<"read">>, Ss, <<".*">>)
),
W = rabbit_data_coercion:to_binary(
proplists:get_value(<<"write">>, Ss, <<".*">>)
),
U0 = #{
name => Username,
tags => proplists:get_value(<<"tags">>, Ss, []),
configure => C,
read => R,
write => W
},
%% rabbit_auth_backend_internal:put_user relies on maps:is_key, can't pass
%% undefined through.
U1 = case proplists:get_value(<<"password">>, Ss, undefined) of
undefined ->
U0;
V ->
U0#{password => rabbit_data_coercion:to_binary(V)}
end,
{true, U1};
_ ->
false
end
end,
Users
).

%%
%% Private
%%

%% Translate underscores to dashes in prop keys.
-spec underscore_to_dash(definitions()) -> definitions().
underscore_to_dash(Props) ->
lists:map(
fun({N, V}) ->
{binary:replace(N, <<"_">>, <<"-">>, [global]), V}
end,
Props
).

%% Add user iff it doesn't exist & set permissions per vhost.
-spec add_user(rabbit_types:vhost(), seeded_user_properties(), rabbit_types:username()) -> ok.
add_user(VHost, #{name := Name, configure := C, write := W, read := R} = User, ActingUser) ->
%% put_user has its own existence check, but it still updates password if the user exists.
%% We want only the newly created users to have password set from the config.
rabbit_auth_backend_internal:exists(Name) orelse
rabbit_auth_backend_internal:put_user(User, ActingUser),
rabbit_auth_backend_internal:set_permissions(Name, VHost, C, W, R, ActingUser).
Loading

0 comments on commit 659f16b

Please sign in to comment.