feat: add authz file rule validator

This commit is contained in:
某文 2023-05-19 08:55:04 +08:00
parent 52e2caa671
commit 6cb9efd7d3
4 changed files with 50 additions and 5 deletions

View File

@ -237,6 +237,8 @@ render_and_load_app_config(App, Opts) ->
end. end.
do_render_app_config(App, Schema, ConfigFile, Opts) -> do_render_app_config(App, Schema, ConfigFile, Opts) ->
%% copy acl_conf must run before read_schema_configs
copy_acl_conf(),
Vars = mustache_vars(App, Opts), Vars = mustache_vars(App, Opts),
RenderedConfigFile = render_config_file(ConfigFile, Vars), RenderedConfigFile = render_config_file(ConfigFile, Vars),
read_schema_configs(Schema, RenderedConfigFile), read_schema_configs(Schema, RenderedConfigFile),
@ -520,6 +522,16 @@ copy_certs(emqx_conf, Dest0) ->
copy_certs(_, _) -> copy_certs(_, _) ->
ok. ok.
copy_acl_conf() ->
Dest = filename:join([code:lib_dir(emqx), "etc/acl.conf"]),
case code:lib_dir(emqx_authz) of
{error, bad_name} ->
(not filelib:is_regular(Dest)) andalso file:write_file(Dest, <<"">>);
_ ->
{ok, _} = file:copy(deps_path(emqx_authz, "etc/acl.conf"), Dest)
end,
ok.
load_config(SchemaModule, Config) -> load_config(SchemaModule, Config) ->
ConfigBin = ConfigBin =
case is_map(Config) of case is_map(Config) of

View File

@ -482,6 +482,8 @@ maybe_write_files(NewSource) ->
write_acl_file(#{<<"rules">> := Rules} = Source0) -> write_acl_file(#{<<"rules">> := Rules} = Source0) ->
AclPath = ?MODULE:acl_conf_file(), AclPath = ?MODULE:acl_conf_file(),
%% Always check if the rules are valid before writing to the file
%% If the rules are invalid, the old file will be kept
ok = check_acl_file_rules(AclPath, Rules), ok = check_acl_file_rules(AclPath, Rules),
ok = write_file(AclPath, Rules), ok = write_file(AclPath, Rules),
Source1 = maps:remove(<<"rules">>, Source0), Source1 = maps:remove(<<"rules">>, Source0),
@ -538,8 +540,7 @@ check_acl_file_rules(Path, Rules) ->
TmpPath = Path ++ ".tmp", TmpPath = Path ++ ".tmp",
try try
ok = write_file(Path, Rules), ok = write_file(Path, Rules),
#{annotations := #{rules := _}} = emqx_authz_file:create(#{path => Path}), emqx_authz_schema:validate_file_rules(Path)
ok
catch catch
throw:Reason -> throw(Reason) throw:Reason -> throw(Reason)
after after

View File

@ -42,7 +42,8 @@
-export([ -export([
headers_no_content_type/1, headers_no_content_type/1,
headers/1 headers/1,
validate_file_rules/1
]). ]).
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
@ -78,7 +79,17 @@ fields("authorization") ->
authz_fields(); authz_fields();
fields(file) -> fields(file) ->
authz_common_fields(file) ++ authz_common_fields(file) ++
[{path, ?HOCON(string(), #{required => true, desc => ?DESC(path)})}]; [
{path,
?HOCON(
string(),
#{
required => true,
validator => fun ?MODULE:validate_file_rules/1,
desc => ?DESC(path)
}
)}
];
fields(http_get) -> fields(http_get) ->
authz_common_fields(http) ++ authz_common_fields(http) ++
http_common_fields() ++ http_common_fields() ++
@ -495,7 +506,7 @@ authz_fields() ->
%% doc_lift is force a root level reference instead of nesting sub-structs %% doc_lift is force a root level reference instead of nesting sub-structs
extra => #{doc_lift => true}, extra => #{doc_lift => true},
%% it is recommended to configure authz sources from dashboard %% it is recommended to configure authz sources from dashboard
%% hance the importance level for config is low %% hence the importance level for config is low
importance => ?IMPORTANCE_LOW importance => ?IMPORTANCE_LOW
} }
)} )}
@ -507,3 +518,10 @@ default_authz() ->
<<"enable">> => true, <<"enable">> => true,
<<"path">> => <<"${EMQX_ETC_DIR}/acl.conf">> <<"path">> => <<"${EMQX_ETC_DIR}/acl.conf">>
}. }.
validate_file_rules(Path) ->
%% Don't need assert the create result here, all error is thrown
%% some test mock the create function
%% #{annotations := #{rules := _}}
_ = emqx_authz_file:create(#{path => Path}),
ok.

View File

@ -23,6 +23,7 @@
"""). """).
array_nodes_test() -> array_nodes_test() ->
ensure_acl_conf(),
ExpectNodes = ['emqx1@127.0.0.1', 'emqx2@127.0.0.1'], ExpectNodes = ['emqx1@127.0.0.1', 'emqx2@127.0.0.1'],
lists:foreach( lists:foreach(
fun(Nodes) -> fun(Nodes) ->
@ -133,6 +134,7 @@ outdated_log_test() ->
validate_log(?OUTDATED_LOG_CONF). validate_log(?OUTDATED_LOG_CONF).
validate_log(Conf) -> validate_log(Conf) ->
ensure_acl_conf(),
BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]), BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
Conf0 = <<BaseConf/binary, (list_to_binary(Conf))/binary>>, Conf0 = <<BaseConf/binary, (list_to_binary(Conf))/binary>>,
{ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}), {ok, ConfMap0} = hocon:binary(Conf0, #{format => richmap}),
@ -199,6 +201,7 @@ log_test() ->
%% erlfmt-ignore %% erlfmt-ignore
log_rotation_count_limit_test() -> log_rotation_count_limit_test() ->
ensure_acl_conf(),
Format = Format =
""" """
log.file { log.file {
@ -271,6 +274,7 @@ log_rotation_count_limit_test() ->
). ).
authn_validations_test() -> authn_validations_test() ->
ensure_acl_conf(),
BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]), BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
OKHttps = to_bin(?BASE_AUTHN_ARRAY, [post, true, <<"https://127.0.0.1:8080">>]), OKHttps = to_bin(?BASE_AUTHN_ARRAY, [post, true, <<"https://127.0.0.1:8080">>]),
@ -328,6 +332,7 @@ authn_validations_test() ->
). ).
listeners_test() -> listeners_test() ->
ensure_acl_conf(),
BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]), BaseConf = to_bin(?BASE_CONF, ["emqx1@127.0.0.1", "emqx1@127.0.0.1"]),
Conf = <<BaseConf/binary, ?LISTENERS>>, Conf = <<BaseConf/binary, ?LISTENERS>>,
@ -402,6 +407,7 @@ authentication_headers(Conf) ->
Headers. Headers.
doc_gen_test() -> doc_gen_test() ->
ensure_acl_conf(),
%% the json file too large to encode. %% the json file too large to encode.
{ {
timeout, timeout,
@ -424,3 +430,11 @@ doc_gen_test() ->
to_bin(Format, Args) -> to_bin(Format, Args) ->
iolist_to_binary(io_lib:format(Format, Args)). iolist_to_binary(io_lib:format(Format, Args)).
ensure_acl_conf() ->
File = emqx_schema:naive_env_interpolation(<<"${EMQX_ETC_DIR}/acl.conf">>),
ok = filelib:ensure_dir(filename:dirname(File)),
case filelib:is_regular(File) of
true -> ok;
false -> file:write_file(File, <<"">>)
end.