Skip to content

Commit 9c8ab76

Browse files
Merge pull request #7436 from rabbitmq/mergify/bp/v3.12.x/pr-7433
Add users on vhost creation per default_users config (with naming changes) (backport #7433)
2 parents 7912478 + 32124ef commit 9c8ab76

File tree

5 files changed

+288
-106
lines changed

5 files changed

+288
-106
lines changed

deps/rabbit/priv/schema/rabbit.schema

+56-49
Original file line numberDiff line numberDiff line change
@@ -616,8 +616,6 @@ end}.
616616
{datatype, string}
617617
]}.
618618

619-
620-
621619
%%
622620
%% Default User / VHost
623621
%% ====================
@@ -681,64 +679,91 @@ fun(Conf) ->
681679
[list_to_binary(Configure), list_to_binary(Read), list_to_binary(Write)]
682680
end}.
683681

682+
%%
683+
%% Extra Default Users
684+
%% ====================
685+
%%
686+
687+
{mapping, "default_users.$name.vhost_pattern", "rabbit.default_users", [
688+
{validators, ["valid_regex"]},
689+
{datatype, string}
690+
]}.
691+
692+
{mapping, "default_users.$name.password", "rabbit.default_users", [
693+
{datatype, string}
694+
]}.
695+
696+
{mapping, "default_users.$name.configure", "rabbit.default_users", [
697+
{validators, ["valid_regex"]},
698+
{datatype, string}
699+
]}.
700+
701+
{mapping, "default_users.$name.read", "rabbit.default_users", [
702+
{validators, ["valid_regex"]},
703+
{datatype, string}
704+
]}.
705+
706+
{mapping, "default_users.$name.write", "rabbit.default_users", [
707+
{validators, ["valid_regex"]},
708+
{datatype, string}
709+
]}.
710+
711+
{mapping, "default_users.$name.tags", "rabbit.default_users", [
712+
{datatype, {list, atom}}
713+
]}.
714+
715+
{translation, "rabbit.default_users", fun(Conf) ->
716+
case rabbit_cuttlefish:aggregate_props(Conf, ["default_users"]) of
717+
[] -> cuttlefish:unset();
718+
Props -> Props
719+
end
720+
end}.
721+
722+
%%
723+
%% Default Policies
724+
%% ====================
725+
%%
726+
684727
{mapping, "default_policies.operator.$id.vhost_pattern", "rabbit.default_policies.operator", [
685-
{include_default, 1},
686-
{commented, ".*"},
687728
{validators, ["valid_regex"]},
688729
{datatype, string}
689730
]}.
690731

691732
{mapping, "default_policies.operator.$id.queue_pattern", "rabbit.default_policies.operator", [
692-
{include_default, 1},
693-
{commented, ".*"},
694733
{validators, ["valid_regex"]},
695734
{datatype, string}
696735
]}.
697736

698737
{mapping, "default_policies.operator.$id.expires", "rabbit.default_policies.operator", [
699-
{include_default, 1},
700-
{commented, "1s"},
701738
{datatype, {duration, ms}}
702739
]}.
703740

704741
{mapping, "default_policies.operator.$id.message_ttl", "rabbit.default_policies.operator", [
705-
{include_default, 1},
706-
{commented, "1s"},
707742
{datatype, {duration, ms}}
708743
]}.
709744

710745
{mapping, "default_policies.operator.$id.max_length", "rabbit.default_policies.operator", [
711-
{include_default, 1},
712-
{commented, 100},
713746
{validators, ["non_zero_positive_integer"]},
714747
{datatype, integer}
715748
]}.
716749

717750
{mapping, "default_policies.operator.$id.max_length_bytes", "rabbit.default_policies.operator", [
718-
{include_default, 1},
719-
{commented, "1GB"},
720751
{validators, ["non_zero_positive_integer"]},
721752
{datatype, bytesize}
722753
]}.
723754

724755
{mapping, "default_policies.operator.$id.max_in_memory_bytes", "rabbit.default_policies.operator", [
725-
{include_default, 1},
726-
{commented, "1GB"},
727756
{validators, ["non_zero_positive_integer"]},
728757
{datatype, bytesize}
729758
]}.
730759

731760
{mapping, "default_policies.operator.$id.max_in_memory_length", "rabbit.default_policies.operator",
732761
[
733-
{include_default, 1},
734-
{commented, 1000},
735762
{validators, ["non_zero_positive_integer"]},
736763
{datatype, integer}
737764
]}.
738765

739766
{mapping, "default_policies.operator.$id.delivery_limit", "rabbit.default_policies.operator", [
740-
{include_default, 1},
741-
{commented, 1},
742767
{validators, ["non_zero_positive_integer"]},
743768
{datatype, integer}
744769
]}.
@@ -759,55 +784,37 @@ end}.
759784
{["default_policies","operator",ID|T],V};
760785
(E) -> E
761786
end),
762-
Props1 = lists:map(
763-
fun({K, Ss}) ->
764-
{K,
765-
lists:map(fun({N, V}) ->
766-
{binary:replace(N, <<"_">>, <<"-">>, [global]), V}
767-
end, Ss)}
768-
end, Props),
769-
case Props1 of
787+
case Props of
770788
[] -> cuttlefish:unset();
771-
_ -> Props1
772-
end,
773-
Props1
789+
Props -> Props
790+
end
774791
end}.
775792

793+
%%
794+
%% Default VHost Limits
795+
%% ====================
796+
%%
797+
776798
{mapping, "default_limits.vhosts.$id.pattern", "rabbit.default_limits.vhosts", [
777-
{include_default, 1},
778-
{commented, ".*"},
779799
{validators, ["valid_regex"]},
780800
{datatype, string}
781801
]}.
782802

783803
{mapping, "default_limits.vhosts.$id.max_connections", "rabbit.default_limits.vhosts", [
784-
{include_default, 1},
785-
{commented, 1000},
786804
{validators, [ "non_zero_positive_integer"]},
787805
{datatype, integer}
788806
]}.
789807

790808
{mapping, "default_limits.vhosts.$id.max_queues", "rabbit.default_limits.vhosts", [
791-
{include_default, 1},
792-
{commented, 100},
793809
{validators, [ "non_zero_positive_integer"]},
794810
{datatype, integer}
795811
]}.
796812

797813
{translation, "rabbit.default_limits.vhosts", fun(Conf) ->
798-
Props = rabbit_cuttlefish:aggregate_props(Conf, ["default_limits", "vhosts"]),
799-
Props1 = lists:map(
800-
fun({K, Ss}) ->
801-
{K,
802-
lists:map(fun({N, V}) ->
803-
{binary:replace(N, <<"_">>, <<"-">>, [global]), V}
804-
end, Ss)}
805-
end, Props),
806-
case Props1 of
814+
case rabbit_cuttlefish:aggregate_props(Conf, ["default_limits", "vhosts"]) of
807815
[] -> cuttlefish:unset();
808-
_ -> Props1
809-
end,
810-
Props1
816+
Props -> Props
817+
end
811818
end}.
812819

813820
%% Tags for default user
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
1+
%% This Source Code Form is subject to the terms of the Mozilla Public
2+
%% License, v. 2.0. If a copy of the MPL was not distributed with this
3+
%% file, You can obtain one at https://mozilla.org/MPL/2.0/.
4+
%%
5+
%% Copyright (c) 2023-2023 VMware, Inc. or its affiliates. All rights reserved.
6+
%%
7+
8+
-module(rabbit_db_vhost_defaults).
9+
10+
-export([apply/2]).
11+
-export([list_limits/1, list_operator_policies/1, list_users/1]).
12+
13+
-type definitions() :: [{binary(), term()}].
14+
15+
-record(seeding_policy, {
16+
name :: binary(),
17+
queue_pattern = <<".*">> :: binary(),
18+
definition = [] :: definitions()
19+
}).
20+
21+
-type seeded_user_properties() :: #{
22+
name := binary(),
23+
configure := binary(),
24+
read := binary(),
25+
write := binary(),
26+
password := binary(),
27+
tags := [atom()],
28+
_ => _
29+
}.
30+
31+
%% Apply all matching defaults to a VHost.
32+
-spec apply(vhost:name(), rabbit_types:username()) -> ok.
33+
apply(VHost, ActingUser) ->
34+
case list_limits(VHost) of
35+
[] ->
36+
ok;
37+
L ->
38+
ok = rabbit_vhost_limit:set(VHost, L, ActingUser),
39+
rabbit_log:info("Applied default limits to vhost '~tp': ~tp", [VHost, L])
40+
end,
41+
lists:foreach(
42+
fun(P) ->
43+
ok = rabbit_policy:set_op(VHost, P#seeding_policy.name, P#seeding_policy.queue_pattern, P#seeding_policy.definition,
44+
undefined, undefined, ActingUser),
45+
rabbit_log:info("Applied default operator policy to vhost '~tp': ~tp", [VHost, P])
46+
end,
47+
list_operator_policies(VHost)
48+
),
49+
lists:foreach(
50+
fun(U) ->
51+
ok = add_user(VHost, U, ActingUser),
52+
rabbit_log:info("Added default user to vhost '~tp': ~tp", [VHost, maps:remove(password, U)])
53+
end,
54+
list_users(VHost)
55+
),
56+
ok.
57+
58+
%%
59+
%% Helpers
60+
%%
61+
62+
%% Limits that were configured with a matching vhost pattern.
63+
-spec list_limits(vhost:name()) -> proplists:proplist().
64+
list_limits(VHost) ->
65+
AllLimits = application:get_env(rabbit, default_limits, []),
66+
VHostLimits = proplists:get_value(vhosts, AllLimits, []),
67+
Match = lists:search(
68+
fun({_, Ss}) ->
69+
RE = proplists:get_value(<<"pattern">>, Ss, ".*"),
70+
re:run(VHost, RE, [{capture, none}]) =:= match
71+
end,
72+
VHostLimits
73+
),
74+
case Match of
75+
{value, {_, Ss}} ->
76+
Ss1 = proplists:delete(<<"pattern">>, Ss),
77+
underscore_to_dash(Ss1);
78+
_ ->
79+
[]
80+
end.
81+
82+
%% Operator policies that were configured with a matching vhost pattern.
83+
-spec list_operator_policies(vhost:name()) -> [#seeding_policy{}].
84+
list_operator_policies(VHost) ->
85+
AllPolicies = application:get_env(rabbit, default_policies, []),
86+
OpPolicies = proplists:get_value(operator, AllPolicies, []),
87+
lists:filtermap(
88+
fun({PolicyName, Ss}) ->
89+
RE = proplists:get_value(<<"vhost_pattern">>, Ss, ".*"),
90+
case re:run(VHost, RE, [{capture, none}]) of
91+
match ->
92+
QPattern = proplists:get_value(<<"queue_pattern">>, Ss, <<".*">>),
93+
Ss1 = proplists:delete(<<"queue_pattern">>, Ss),
94+
Ss2 = proplists:delete(<<"vhost_pattern">>, Ss1),
95+
{true, #seeding_policy{
96+
name = PolicyName,
97+
queue_pattern = QPattern,
98+
definition = underscore_to_dash(Ss2)
99+
}};
100+
_ ->
101+
false
102+
end
103+
end,
104+
OpPolicies
105+
).
106+
107+
%% Users (permissions) that were configured with a matching vhost pattern.
108+
-spec list_users(vhost:name()) -> [seeded_user_properties()].
109+
list_users(VHost) ->
110+
Users = application:get_env(rabbit, default_users, []),
111+
lists:filtermap(
112+
fun({Username, Ss}) ->
113+
RE = proplists:get_value(<<"vhost_pattern">>, Ss, ".*"),
114+
case re:run(VHost, RE, [{capture, none}]) of
115+
match ->
116+
C = rabbit_data_coercion:to_binary(
117+
proplists:get_value(<<"configure">>, Ss, <<".*">>)
118+
),
119+
R = rabbit_data_coercion:to_binary(
120+
proplists:get_value(<<"read">>, Ss, <<".*">>)
121+
),
122+
W = rabbit_data_coercion:to_binary(
123+
proplists:get_value(<<"write">>, Ss, <<".*">>)
124+
),
125+
U0 = #{
126+
name => Username,
127+
tags => proplists:get_value(<<"tags">>, Ss, []),
128+
configure => C,
129+
read => R,
130+
write => W
131+
},
132+
%% rabbit_auth_backend_internal:put_user relies on maps:is_key, can't pass
133+
%% undefined through.
134+
U1 = case proplists:get_value(<<"password">>, Ss, undefined) of
135+
undefined ->
136+
U0;
137+
V ->
138+
U0#{password => rabbit_data_coercion:to_binary(V)}
139+
end,
140+
{true, U1};
141+
_ ->
142+
false
143+
end
144+
end,
145+
Users
146+
).
147+
148+
%%
149+
%% Private
150+
%%
151+
152+
%% Translate underscores to dashes in prop keys.
153+
-spec underscore_to_dash(definitions()) -> definitions().
154+
underscore_to_dash(Props) ->
155+
lists:map(
156+
fun({N, V}) ->
157+
{binary:replace(N, <<"_">>, <<"-">>, [global]), V}
158+
end,
159+
Props
160+
).
161+
162+
%% Add user iff it doesn't exist & set permissions per vhost.
163+
-spec add_user(rabbit_types:vhost(), seeded_user_properties(), rabbit_types:username()) -> ok.
164+
add_user(VHost, #{name := Name, configure := C, write := W, read := R} = User, ActingUser) ->
165+
%% put_user has its own existence check, but it still updates password if the user exists.
166+
%% We want only the newly created users to have password set from the config.
167+
rabbit_auth_backend_internal:exists(Name) orelse
168+
rabbit_auth_backend_internal:put_user(User, ActingUser),
169+
rabbit_auth_backend_internal:set_permissions(Name, VHost, C, W, R, ActingUser).

0 commit comments

Comments
 (0)