Skip to content

Commit 60e074d

Browse files
Merge pull request #8388 from rabbitmq/rabbitmq-server-8372
Fail boot if definition file is invalid JSON
2 parents d9c5bd8 + a68e2d3 commit 60e074d

File tree

4 files changed

+155
-43
lines changed

4 files changed

+155
-43
lines changed

deps/rabbit/src/rabbit_definitions.erl

Lines changed: 64 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@
4444
-export([import_raw/1, import_raw/2, import_parsed/1, import_parsed/2,
4545
import_parsed_with_hashing/1, import_parsed_with_hashing/2,
4646
apply_defs/2, apply_defs/3,
47-
should_skip_if_unchanged/0]).
47+
should_skip_if_unchanged/0
48+
]).
4849

4950
-export([all_definitions/0]).
5051
-export([
@@ -53,7 +54,13 @@
5354
list_exchanges/0, list_queues/0, list_bindings/0,
5455
is_internal_parameter/1
5556
]).
56-
-export([decode/1, decode/2, args/1]).
57+
-export([decode/1, decode/2, args/1, validate_definitions/1]).
58+
59+
%% for tests
60+
-export([
61+
maybe_load_definitions_from_local_filesystem_if_unchanged/3,
62+
maybe_load_definitions_from_pluggable_source_if_unchanged/2
63+
]).
5764

5865
-import(rabbit_misc, [pget/2, pget/3]).
5966
-import(rabbit_data_coercion, [to_binary/1]).
@@ -97,6 +104,21 @@ maybe_load_definitions() ->
97104
{error, E} -> {error, E}
98105
end.
99106

107+
validate_definitions(Defs) when is_list(Defs) ->
108+
lists:foldl(fun(_Body, false) ->
109+
false;
110+
(Body, true) ->
111+
case decode(Body) of
112+
{ok, _Map} -> true;
113+
{error, _Err} -> false
114+
end
115+
end, true, Defs);
116+
validate_definitions(Body) when is_binary(Body) ->
117+
case decode(Body) of
118+
{ok, _Map} -> true;
119+
{error, _Err} -> false
120+
end.
121+
100122
-spec import_raw(Body :: binary() | iolist()) -> ok | {error, term()}.
101123
import_raw(Body) ->
102124
rabbit_log:info("Asked to import definitions. Acting user: ~ts", [?INTERNAL_USER]),
@@ -271,20 +293,25 @@ maybe_load_definitions_from_local_filesystem(App, Key) ->
271293
rabbit_log:debug("Will re-import definitions even if they have not changed"),
272294
Mod:load(IsDir, Path);
273295
true ->
274-
Algo = rabbit_definitions_hashing:hashing_algorithm(),
275-
rabbit_log:debug("Will import definitions only if definition file/directory has changed, hashing algo: ~ts", [Algo]),
276-
CurrentHash = rabbit_definitions_hashing:stored_global_hash(),
277-
rabbit_log:debug("Previously stored hash value of imported definitions: ~ts...", [binary:part(rabbit_misc:hexify(CurrentHash), 0, 12)]),
278-
case Mod:load_with_hashing(IsDir, Path, CurrentHash, Algo) of
279-
CurrentHash ->
280-
rabbit_log:info("Hash value of imported definitions matches current contents");
281-
UpdatedHash ->
282-
rabbit_log:debug("Hash value of imported definitions has changed to ~ts", [binary:part(rabbit_misc:hexify(UpdatedHash), 0, 12)]),
283-
rabbit_definitions_hashing:store_global_hash(UpdatedHash)
284-
end
296+
maybe_load_definitions_from_local_filesystem_if_unchanged(Mod, IsDir, Path)
285297
end
286298
end.
287299

300+
maybe_load_definitions_from_local_filesystem_if_unchanged(Mod, IsDir, Path) ->
301+
Algo = rabbit_definitions_hashing:hashing_algorithm(),
302+
rabbit_log:debug("Will import definitions only if definition file/directory has changed, hashing algo: ~ts", [Algo]),
303+
CurrentHash = rabbit_definitions_hashing:stored_global_hash(),
304+
rabbit_log:debug("Previously stored hash value of imported definitions: ~ts...", [binary:part(rabbit_misc:hexify(CurrentHash), 0, 12)]),
305+
case Mod:load_with_hashing(IsDir, Path, CurrentHash, Algo) of
306+
{error, Err} ->
307+
{error, Err};
308+
CurrentHash ->
309+
rabbit_log:info("Hash value of imported definitions matches current contents");
310+
UpdatedHash ->
311+
rabbit_log:debug("Hash value of imported definitions has changed to ~ts", [binary:part(rabbit_misc:hexify(UpdatedHash), 0, 12)]),
312+
rabbit_definitions_hashing:store_global_hash(UpdatedHash)
313+
end.
314+
288315
maybe_load_definitions_from_pluggable_source(App, Key) ->
289316
case application:get_env(App, Key) of
290317
undefined -> ok;
@@ -296,26 +323,33 @@ maybe_load_definitions_from_pluggable_source(App, Key) ->
296323
{error, "definition import source is configured but definitions.import_backend is not set"};
297324
ModOrAlias ->
298325
Mod = normalize_backend_module(ModOrAlias),
299-
case should_skip_if_unchanged() of
300-
false ->
301-
rabbit_log:debug("Will use module ~ts to import definitions", [Mod]),
302-
Mod:load(Proplist);
303-
true ->
304-
rabbit_log:debug("Will use module ~ts to import definitions (if definition file/directory/source has changed)", [Mod]),
305-
CurrentHash = rabbit_definitions_hashing:stored_global_hash(),
306-
rabbit_log:debug("Previously stored hash value of imported definitions: ~ts...", [binary:part(rabbit_misc:hexify(CurrentHash), 0, 12)]),
307-
Algo = rabbit_definitions_hashing:hashing_algorithm(),
308-
case Mod:load_with_hashing(Proplist, CurrentHash, Algo) of
309-
CurrentHash ->
310-
rabbit_log:info("Hash value of imported definitions matches current contents");
311-
UpdatedHash ->
312-
rabbit_log:debug("Hash value of imported definitions has changed to ~ts...", [binary:part(rabbit_misc:hexify(CurrentHash), 0, 12)]),
313-
rabbit_definitions_hashing:store_global_hash(UpdatedHash)
314-
end
315-
end
326+
maybe_load_definitions_from_pluggable_source_if_unchanged(Mod, Proplist)
316327
end
317328
end.
318329

330+
maybe_load_definitions_from_pluggable_source_if_unchanged(Mod, Proplist) ->
331+
case should_skip_if_unchanged() of
332+
false ->
333+
rabbit_log:debug("Will use module ~ts to import definitions", [Mod]),
334+
Mod:load(Proplist);
335+
true ->
336+
rabbit_log:debug("Will use module ~ts to import definitions (if definition file/directory/source has changed)", [Mod]),
337+
CurrentHash = rabbit_definitions_hashing:stored_global_hash(),
338+
rabbit_log:debug("Previously stored hash value of imported definitions: ~ts...", [binary:part(rabbit_misc:hexify(CurrentHash), 0, 12)]),
339+
Algo = rabbit_definitions_hashing:hashing_algorithm(),
340+
case Mod:load_with_hashing(Proplist, CurrentHash, Algo) of
341+
{error, Err} ->
342+
{error, Err};
343+
CurrentHash ->
344+
rabbit_log:info("Hash value of imported definitions matches current contents");
345+
UpdatedHash ->
346+
rabbit_log:debug("Hash value of imported definitions has changed to ~ts...", [binary:part(rabbit_misc:hexify(CurrentHash), 0, 12)]),
347+
rabbit_definitions_hashing:store_global_hash(UpdatedHash)
348+
end
349+
end.
350+
351+
352+
319353
normalize_backend_module(local_filesystem) ->
320354
rabbit_definitions_import_local_filesystem;
321355
normalize_backend_module(local) ->

deps/rabbit/src/rabbit_definitions_import_local_filesystem.erl

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535

3636
-import(rabbit_misc, [pget/2, pget/3]).
3737
-import(rabbit_data_coercion, [to_binary/1]).
38-
-import(rabbit_definitions, [import_raw/1]).
38+
-import(rabbit_definitions, [import_raw/1, decode/1, validate_definitions/1]).
3939

4040
%%
4141
%% API
@@ -84,12 +84,18 @@ load_with_hashing(IsDir, Path, PreviousHash, Algo) when is_boolean(IsDir) ->
8484
[] ->
8585
rabbit_definitions_hashing:hash(Algo, undefined);
8686
Defs ->
87-
case rabbit_definitions_hashing:hash(Algo, Defs) of
88-
PreviousHash -> PreviousHash;
89-
Other ->
90-
rabbit_log:debug("New hash: ~ts", [rabbit_misc:hexify(Other)]),
91-
_ = load_from_local_path(IsDir, Path),
92-
Other
87+
case validate_definitions(Defs) of
88+
true ->
89+
case rabbit_definitions_hashing:hash(Algo, Defs) of
90+
PreviousHash -> PreviousHash;
91+
Other ->
92+
rabbit_log:debug("New hash: ~ts", [rabbit_misc:hexify(Other)]),
93+
_ = load_from_local_path(IsDir, Path),
94+
Other
95+
end;
96+
false ->
97+
rabbit_log:error("Failed to parse a definition file, path: ~p", [Path]),
98+
{error, not_json}
9399
end
94100
end.
95101

deps/rabbit/test/definition_import_SUITE.erl

Lines changed: 32 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ groups() ->
5151
import_case16,
5252
import_case17,
5353
import_case18,
54-
import_case19
54+
import_case19,
55+
import_case20
5556
]},
5657

5758
{boot_time_import_using_classic_source, [], [
@@ -295,6 +296,15 @@ import_case19(Config) ->
295296
{skip, "Should not run in mixed version environments"}
296297
end.
297298

299+
import_case20(Config) ->
300+
case rabbit_ct_helpers:is_mixed_versions() of
301+
false ->
302+
import_invalid_file_case_if_unchanged(Config, "failing_case20");
303+
true ->
304+
%% skip the test in mixed version mode
305+
{skip, "Should not run in mixed version environments"}
306+
end.
307+
298308
export_import_round_trip_case1(Config) ->
299309
case rabbit_ct_helpers:is_mixed_versions() of
300310
false ->
@@ -379,6 +389,11 @@ import_invalid_file_case(Config, CaseName) ->
379389
rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, run_invalid_import_case, [CasePath]),
380390
ok.
381391

392+
import_invalid_file_case_if_unchanged(Config, CaseName) ->
393+
CasePath = filename:join(?config(data_dir, Config), CaseName ++ ".json"),
394+
rabbit_ct_broker_helpers:rpc(Config, 0, ?MODULE, run_invalid_import_case_if_unchanged, [CasePath]),
395+
ok.
396+
382397
import_from_directory_case(Config, CaseName) ->
383398
import_from_directory_case_expect(Config, CaseName, ok).
384399

@@ -398,15 +413,15 @@ import_raw(Config, Body) ->
398413
ok -> ok;
399414
{error, E} ->
400415
ct:pal("Import of JSON definitions ~tp failed: ~tp~n", [Body, E]),
401-
ct:fail({failure, Body, E})
416+
ct:fail({expected_failure, Body, E})
402417
end.
403418

404419
import_parsed(Config, Body) ->
405420
case rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_definitions, import_parsed, [Body]) of
406421
ok -> ok;
407422
{error, E} ->
408423
ct:pal("Import of parsed definitions ~tp failed: ~tp~n", [Body, E]),
409-
ct:fail({failure, Body, E})
424+
ct:fail({expected_failure, Body, E})
410425
end.
411426

412427
export(Config) ->
@@ -432,19 +447,30 @@ run_import_case(Path) ->
432447
ok -> ok;
433448
{error, E} ->
434449
ct:pal("Import case ~tp failed: ~tp~n", [Path, E]),
435-
ct:fail({failure, Path, E})
450+
ct:fail({expected_failure, Path, E})
436451
end.
437452

438453
run_invalid_import_case(Path) ->
439454
{ok, Body} = file:read_file(Path),
440-
ct:pal("Successfully loaded a definition to import from ~tp~n", [Path]),
455+
ct:pal("Successfully loaded a definition file at ~tp~n", [Path]),
441456
case rabbit_definitions:import_raw(Body) of
442457
ok ->
443458
ct:pal("Expected import case ~tp to fail~n", [Path]),
444-
ct:fail({failure, Path});
459+
ct:fail({expected_failure, Path});
445460
{error, _E} -> ok
446461
end.
447462

463+
run_invalid_import_case_if_unchanged(Path) ->
464+
Mod = rabbit_definitions_import_local_filesystem,
465+
ct:pal("Successfully loaded a definition to import from ~tp~n", [Path]),
466+
case rabbit_definitions:maybe_load_definitions_from_local_filesystem_if_unchanged(Mod, false, Path) of
467+
ok ->
468+
ct:pal("Expected import case ~tp to fail~n", [Path]),
469+
ct:fail({expected_failure, Path});
470+
{error, _E} -> ok
471+
end.
472+
473+
448474
queue_lookup(Config, VHost, Name) ->
449475
rabbit_ct_broker_helpers:rpc(Config, 0, rabbit_amqqueue, lookup, [rabbit_misc:r(VHost, queue, Name)]).
450476

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
{
2+
"bindings": [],
3+
"exchanges": [],
4+
"global_parameters": [
5+
{
6+
"name": "cluster_name"""""{{{,
7+
"value": "rabbitmq@localhost",{{{{
8+
}
9+
],
10+
"parameters": [],
11+
"permissions": [
12+
{
13+
"configure": ".*",
14+
"read": ".*",
15+
"user": "guest",
16+
"vhost": "/",
17+
"write"$ ".*"
18+
}
19+
],
20+
"policies": [],
21+
"queues": [],
22+
"rabbit_version": "3.9.1",
23+
"rabbitmq_version": "3.9.1",
24+
"topic_permissions": [],
25+
"users": [
26+
{
27+
"hashing_algorithm": "rabbit_password_hashing_sha256",
28+
"limits": {"max-connections" : 2},
29+
"name": "limited_guest",
30+
"password_hash": "wS4AT3B4Z5RpWlFn1FA30osf2C75D7WA3gem591ACDZ6saO6",
31+
"tags": [
32+
"administrator"
33+
]
34+
}
35+
],
36+
"vhosts": [
37+
{
38+
"limits": [],
39+
"name": "/"
40+
},
41+
{
42+
"limits": [],
43+
"name": "tagged"
44+
}
45+
]
46+
}

0 commit comments

Comments
 (0)