fix(authn): save certificates to certs dir
This commit is contained in:
parent
71d2e6bebd
commit
a7413bc11e
|
@ -66,6 +66,7 @@
|
||||||
, remove_config/2
|
, remove_config/2
|
||||||
, reset_config/2
|
, reset_config/2
|
||||||
, data_dir/0
|
, data_dir/0
|
||||||
|
, certs_dir/0
|
||||||
]).
|
]).
|
||||||
|
|
||||||
-define(APP, ?MODULE).
|
-define(APP, ?MODULE).
|
||||||
|
@ -250,3 +251,6 @@ reset_config([RootName | _] = KeyPath, Opts) ->
|
||||||
|
|
||||||
data_dir() ->
|
data_dir() ->
|
||||||
application:get_env(emqx, data_dir, "data").
|
application:get_env(emqx, data_dir, "data").
|
||||||
|
|
||||||
|
certs_dir() ->
|
||||||
|
filename:join([data_dir(), certs]).
|
||||||
|
|
|
@ -56,29 +56,24 @@
|
||||||
-spec pre_config_update(update_request(), emqx_config:raw_config())
|
-spec pre_config_update(update_request(), emqx_config:raw_config())
|
||||||
-> {ok, map() | list()} | {error, term()}.
|
-> {ok, map() | list()} | {error, term()}.
|
||||||
pre_config_update(UpdateReq, OldConfig) ->
|
pre_config_update(UpdateReq, OldConfig) ->
|
||||||
case do_pre_config_update(UpdateReq, to_list(OldConfig)) of
|
try do_pre_config_update(UpdateReq, to_list(OldConfig)) of
|
||||||
{error, Reason} -> {error, Reason};
|
{error, Reason} -> {error, Reason};
|
||||||
{ok, NewConfig} -> {ok, return_map(NewConfig)}
|
{ok, NewConfig} -> {ok, return_map(NewConfig)}
|
||||||
|
catch
|
||||||
|
throw : Reason ->
|
||||||
|
{error, Reason}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) ->
|
do_pre_config_update({create_authenticator, ChainName, Config}, OldConfig) ->
|
||||||
try
|
|
||||||
CertsDir = certs_dir(ChainName, Config),
|
CertsDir = certs_dir(ChainName, Config),
|
||||||
NConfig = convert_certs(CertsDir, Config),
|
NConfig = convert_certs(CertsDir, Config),
|
||||||
{ok, OldConfig ++ [NConfig]}
|
{ok, OldConfig ++ [NConfig]};
|
||||||
catch
|
|
||||||
error:{save_cert_to_file, _} = Reason ->
|
|
||||||
{error, Reason};
|
|
||||||
error:{missing_parameter, _} = Reason ->
|
|
||||||
{error, Reason}
|
|
||||||
end;
|
|
||||||
do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) ->
|
do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) ->
|
||||||
NewConfig = lists:filter(fun(OldConfig0) ->
|
NewConfig = lists:filter(fun(OldConfig0) ->
|
||||||
AuthenticatorID =/= authenticator_id(OldConfig0)
|
AuthenticatorID =/= authenticator_id(OldConfig0)
|
||||||
end, OldConfig),
|
end, OldConfig),
|
||||||
{ok, NewConfig};
|
{ok, NewConfig};
|
||||||
do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) ->
|
do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config}, OldConfig) ->
|
||||||
try
|
|
||||||
CertsDir = certs_dir(ChainName, AuthenticatorID),
|
CertsDir = certs_dir(ChainName, AuthenticatorID),
|
||||||
NewConfig = lists:map(
|
NewConfig = lists:map(
|
||||||
fun(OldConfig0) ->
|
fun(OldConfig0) ->
|
||||||
|
@ -87,13 +82,7 @@ do_pre_config_update({update_authenticator, ChainName, AuthenticatorID, Config},
|
||||||
false -> OldConfig0
|
false -> OldConfig0
|
||||||
end
|
end
|
||||||
end, OldConfig),
|
end, OldConfig),
|
||||||
{ok, NewConfig}
|
{ok, NewConfig};
|
||||||
catch
|
|
||||||
error:{save_cert_to_file, _} = Reason ->
|
|
||||||
{error, Reason};
|
|
||||||
error:{missing_parameter, _} = Reason ->
|
|
||||||
{error, Reason}
|
|
||||||
end;
|
|
||||||
do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) ->
|
do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) ->
|
||||||
case split_by_id(AuthenticatorID, OldConfig) of
|
case split_by_id(AuthenticatorID, OldConfig) of
|
||||||
{error, Reason} -> {error, Reason};
|
{error, Reason} -> {error, Reason};
|
||||||
|
@ -152,7 +141,7 @@ do_check_conifg(Config, Providers) ->
|
||||||
?SLOG(warning, #{msg => "unknown_authn_type",
|
?SLOG(warning, #{msg => "unknown_authn_type",
|
||||||
type => Type,
|
type => Type,
|
||||||
providers => Providers}),
|
providers => Providers}),
|
||||||
throw(unknown_authn_type);
|
throw({unknown_authn_type, Type});
|
||||||
Module ->
|
Module ->
|
||||||
do_check_conifg(Type, Config, Module)
|
do_check_conifg(Type, Config, Module)
|
||||||
end.
|
end.
|
||||||
|
@ -180,7 +169,7 @@ do_check_conifg(Type, Config, Module) ->
|
||||||
reason => E,
|
reason => E,
|
||||||
stacktrace => S
|
stacktrace => S
|
||||||
}),
|
}),
|
||||||
throw(bad_authenticator_config)
|
throw({bad_authenticator_config, #{type => Type, reason => E}})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
return_map([L]) -> L;
|
return_map([L]) -> L;
|
||||||
|
@ -197,7 +186,7 @@ convert_certs(CertsDir, Config) ->
|
||||||
new_ssl_config(Config, SSL);
|
new_ssl_config(Config, SSL);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, Reason#{msg => bad_ssl_config}),
|
?SLOG(error, Reason#{msg => bad_ssl_config}),
|
||||||
throw(bad_ssl_config)
|
throw({bad_ssl_config, Reason})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
convert_certs(CertsDir, NewConfig, OldConfig) ->
|
convert_certs(CertsDir, NewConfig, OldConfig) ->
|
||||||
|
@ -209,7 +198,7 @@ convert_certs(CertsDir, NewConfig, OldConfig) ->
|
||||||
new_ssl_config(NewConfig, NewSSL1);
|
new_ssl_config(NewConfig, NewSSL1);
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
?SLOG(error, Reason#{msg => bad_ssl_config}),
|
?SLOG(error, Reason#{msg => bad_ssl_config}),
|
||||||
throw(bad_ssl_config)
|
throw({bad_ssl_config, Reason})
|
||||||
end.
|
end.
|
||||||
|
|
||||||
new_ssl_config(Config, undefined) -> Config;
|
new_ssl_config(Config, undefined) -> Config;
|
||||||
|
@ -257,8 +246,8 @@ authenticator_id(#{<<"mechanism">> := Mechanism, <<"backend">> := Backend}) ->
|
||||||
<<Mechanism/binary, ":", Backend/binary>>;
|
<<Mechanism/binary, ":", Backend/binary>>;
|
||||||
authenticator_id(#{<<"mechanism">> := Mechanism}) ->
|
authenticator_id(#{<<"mechanism">> := Mechanism}) ->
|
||||||
Mechanism;
|
Mechanism;
|
||||||
authenticator_id(C) ->
|
authenticator_id(_C) ->
|
||||||
error({missing_parameter, mechanism, C}).
|
throw({missing_parameter, #{name => mechanism}}).
|
||||||
|
|
||||||
%% @doc Make the authentication type.
|
%% @doc Make the authentication type.
|
||||||
authn_type(#{mechanism := M, backend := B}) -> {atom(M), atom(B)};
|
authn_type(#{mechanism := M, backend := B}) -> {atom(M), atom(B)};
|
||||||
|
@ -270,7 +259,13 @@ atom(Bin) ->
|
||||||
binary_to_existing_atom(Bin, utf8).
|
binary_to_existing_atom(Bin, utf8).
|
||||||
|
|
||||||
%% The relative dir for ssl files.
|
%% The relative dir for ssl files.
|
||||||
certs_dir(ChainName, ID) when is_binary(ID) ->
|
certs_dir(ChainName, ConfigOrID) ->
|
||||||
filename:join([to_bin(ChainName), ID]);
|
DirName = dir(ChainName, ConfigOrID),
|
||||||
certs_dir(ChainName, Config) when is_map(Config) ->
|
SubDir = iolist_to_binary(filename:join(["authn", DirName])),
|
||||||
certs_dir(ChainName, authenticator_id(Config)).
|
binary:replace(SubDir, <<":">>, <<"-">>, [global]).
|
||||||
|
|
||||||
|
dir(ChainName, ID) when is_binary(ID) ->
|
||||||
|
binary:replace(iolist_to_binary([to_bin(ChainName), "-", ID]), <<":">>, <<"-">>);
|
||||||
|
dir(ChainName, Config) when is_map(Config) ->
|
||||||
|
dir(ChainName, authenticator_id(Config)).
|
||||||
|
|
||||||
|
|
|
@ -292,8 +292,8 @@ do_ensure_ssl_file(Dir, Key, Opts, MaybePem, DryRun) ->
|
||||||
true -> {ok, Opts};
|
true -> {ok, Opts};
|
||||||
{error, enoent} when DryRun -> {ok, Opts};
|
{error, enoent} when DryRun -> {ok, Opts};
|
||||||
{error, Reason} ->
|
{error, Reason} ->
|
||||||
{error, #{file_path => MaybePem,
|
{error, #{pem_check => invalid_pem,
|
||||||
reason => Reason
|
file_read => Reason
|
||||||
}}
|
}}
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
@ -333,12 +333,16 @@ save_pem_file(Dir, Key, Pem, DryRun) ->
|
||||||
|
|
||||||
%% compute the filename for a PEM format key/certificate
|
%% compute the filename for a PEM format key/certificate
|
||||||
%% the filename is prefixed by the option name without the 'file' part
|
%% the filename is prefixed by the option name without the 'file' part
|
||||||
%% and suffixed with the first 8 byets of base64 encode result of the PEM content's
|
%% and suffixed with the first 8 byets the PEM content's md5 checksum.
|
||||||
%% md5 checksum. e.g. key-EKjjO9um, cert-TwuCW1vh, and cacert-6ZaWqNuC
|
%% e.g. key-1234567890abcdef, cert-1234567890abcdef, and cacert-1234567890abcdef
|
||||||
pem_file_name(Dir, Key, Pem) ->
|
pem_file_name(Dir, Key, Pem) ->
|
||||||
<<CK:8/binary, _/binary>> = base64:encode(crypto:hash(md5, Pem)),
|
<<CK:8/binary, _/binary>> = crypto:hash(md5, Pem),
|
||||||
FileName = binary:replace(Key, <<"file">>, <<"-", CK/binary>>),
|
Suffix = hex_str(CK),
|
||||||
filename:join([emqx:data_dir(), Dir, FileName]).
|
FileName = binary:replace(Key, <<"file">>, <<"-", Suffix/binary>>),
|
||||||
|
filename:join([emqx:certs_dir(), Dir, FileName]).
|
||||||
|
|
||||||
|
hex_str(Bin) ->
|
||||||
|
iolist_to_binary([io_lib:format("~2.16.0b",[X]) || <<X:8>> <= Bin ]).
|
||||||
|
|
||||||
is_valid_pem_file(Path) ->
|
is_valid_pem_file(Path) ->
|
||||||
case file:read_file(Path) of
|
case file:read_file(Path) of
|
||||||
|
|
|
@ -79,7 +79,7 @@ ssl_files_failure_test_() ->
|
||||||
{"enoent_key_file",
|
{"enoent_key_file",
|
||||||
fun() ->
|
fun() ->
|
||||||
NonExistingFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))),
|
NonExistingFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))),
|
||||||
?assertMatch({error, #{reason := enoent}},
|
?assertMatch({error, #{file_read := enoent, pem_check := invalid_pem}},
|
||||||
emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"keyfile">> => NonExistingFile}))
|
emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"keyfile">> => NonExistingFile}))
|
||||||
end},
|
end},
|
||||||
{"bad_pem_string",
|
{"bad_pem_string",
|
||||||
|
@ -93,7 +93,7 @@ ssl_files_failure_test_() ->
|
||||||
TmpFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))),
|
TmpFile = filename:join("/tmp", integer_to_list(erlang:system_time(microsecond))),
|
||||||
try
|
try
|
||||||
ok = file:write_file(TmpFile, <<"not a valid pem">>),
|
ok = file:write_file(TmpFile, <<"not a valid pem">>),
|
||||||
?assertMatch({error, #{file_path := _, reason := not_pem}},
|
?assertMatch({error, #{file_read := not_pem}},
|
||||||
emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"cacertfile">> => bin(TmpFile)}))
|
emqx_tls_lib:ensure_ssl_files("/tmp", #{<<"cacertfile">> => bin(TmpFile)}))
|
||||||
after
|
after
|
||||||
file:delete(TmpFile)
|
file:delete(TmpFile)
|
||||||
|
@ -106,7 +106,7 @@ ssl_files_save_delete_test() ->
|
||||||
Dir = filename:join(["/tmp", "ssl-test-dir"]),
|
Dir = filename:join(["/tmp", "ssl-test-dir"]),
|
||||||
{ok, SSL} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0),
|
{ok, SSL} = emqx_tls_lib:ensure_ssl_files(Dir, SSL0),
|
||||||
File = maps:get(<<"keyfile">>, SSL),
|
File = maps:get(<<"keyfile">>, SSL),
|
||||||
?assertMatch(<<"/tmp/ssl-test-dir/key-", _:8/binary>>, File),
|
?assertMatch(<<"/tmp/ssl-test-dir/key-", _:16/binary>>, File),
|
||||||
?assertEqual({ok, bin(test_key())}, file:read_file(File)),
|
?assertEqual({ok, bin(test_key())}, file:read_file(File)),
|
||||||
%% no old file to delete
|
%% no old file to delete
|
||||||
ok = emqx_tls_lib:delete_ssl_files(Dir, SSL, undefined),
|
ok = emqx_tls_lib:delete_ssl_files(Dir, SSL, undefined),
|
||||||
|
|
|
@ -1989,69 +1989,46 @@ convert_certs(Config) ->
|
||||||
|
|
||||||
serialize_error({not_found, {authenticator, ID}}) ->
|
serialize_error({not_found, {authenticator, ID}}) ->
|
||||||
{404, #{code => <<"NOT_FOUND">>,
|
{404, #{code => <<"NOT_FOUND">>,
|
||||||
message => list_to_binary(
|
message => binfmt("Authenticator '~ts' does not exist", [ID]) }};
|
||||||
io_lib:format("Authenticator '~ts' does not exist", [ID])
|
|
||||||
)}};
|
|
||||||
|
|
||||||
serialize_error({not_found, {listener, ID}}) ->
|
serialize_error({not_found, {listener, ID}}) ->
|
||||||
{404, #{code => <<"NOT_FOUND">>,
|
{404, #{code => <<"NOT_FOUND">>,
|
||||||
message => list_to_binary(
|
message => binfmt("Listener '~ts' does not exist", [ID])}};
|
||||||
io_lib:format("Listener '~ts' does not exist", [ID])
|
|
||||||
)}};
|
|
||||||
|
|
||||||
serialize_error({not_found, {chain, ?GLOBAL}}) ->
|
serialize_error({not_found, {chain, ?GLOBAL}}) ->
|
||||||
{500, #{code => <<"INTERNAL_SERVER_ERROR">>,
|
{404, #{code => <<"NOT_FOUND">>,
|
||||||
message => <<"Authentication status is abnormal">>}};
|
message => <<"Authenticator not found in the 'global' scope">>}};
|
||||||
|
|
||||||
serialize_error({not_found, {chain, Name}}) ->
|
serialize_error({not_found, {chain, Name}}) ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
message => list_to_binary(
|
message => binfmt("No authentication has been create for listener '~ts'", [Name])}};
|
||||||
io_lib:format("No authentication has been create for listener '~ts'", [Name])
|
|
||||||
)}};
|
|
||||||
|
|
||||||
serialize_error({already_exists, {authenticator, ID}}) ->
|
serialize_error({already_exists, {authenticator, ID}}) ->
|
||||||
{409, #{code => <<"ALREADY_EXISTS">>,
|
{409, #{code => <<"ALREADY_EXISTS">>,
|
||||||
message => list_to_binary(
|
message => binfmt("Authenticator '~ts' already exist", [ID])}};
|
||||||
io_lib:format("Authenticator '~ts' already exist", [ID])
|
|
||||||
)}};
|
|
||||||
|
|
||||||
serialize_error(no_available_provider) ->
|
serialize_error(no_available_provider) ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
message => <<"Unsupported authentication type">>}};
|
message => <<"Unsupported authentication type">>}};
|
||||||
|
|
||||||
serialize_error(change_of_authentication_type_is_not_allowed) ->
|
serialize_error(change_of_authentication_type_is_not_allowed) ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
message => <<"Change of authentication type is not allowed">>}};
|
message => <<"Change of authentication type is not allowed">>}};
|
||||||
|
|
||||||
serialize_error(unsupported_operation) ->
|
serialize_error(unsupported_operation) ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
message => <<"Operation not supported in this authentication type">>}};
|
message => <<"Operation not supported in this authentication type">>}};
|
||||||
|
serialize_error({bad_ssl_config, Details}) ->
|
||||||
serialize_error({save_cert_to_file, invalid_certificate}) ->
|
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
message => <<"Invalid certificate">>}};
|
message => binfmt("bad_ssl_config ~p", [Details])}};
|
||||||
|
serialize_error({missing_parameter, Detail}) ->
|
||||||
serialize_error({save_cert_to_file, {_, Reason}}) ->
|
|
||||||
{500, #{code => <<"INTERNAL_SERVER_ERROR">>,
|
|
||||||
message => list_to_binary(
|
|
||||||
io_lib:format("Cannot save certificate to file due to '~p'", [Reason])
|
|
||||||
)}};
|
|
||||||
|
|
||||||
serialize_error({missing_parameter, Name}) ->
|
|
||||||
{400, #{code => <<"MISSING_PARAMETER">>,
|
{400, #{code => <<"MISSING_PARAMETER">>,
|
||||||
message => list_to_binary(
|
message => binfmt("Missing required parameter", [Detail])}};
|
||||||
io_lib:format("The input parameter '~p' that is mandatory for processing this request is not supplied", [Name])
|
|
||||||
)}};
|
|
||||||
|
|
||||||
serialize_error({invalid_parameter, Name}) ->
|
serialize_error({invalid_parameter, Name}) ->
|
||||||
{400, #{code => <<"INVALID_PARAMETER">>,
|
{400, #{code => <<"INVALID_PARAMETER">>,
|
||||||
message => list_to_binary(
|
message => binfmt("Invalid value for '~p'", [Name])}};
|
||||||
io_lib:format("The value of input parameter '~p' is invalid", [Name])
|
serialize_error({unknown_authn_type, Type}) ->
|
||||||
)}};
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
|
message => binfmt("Unknown type '~ts'", [Type])}};
|
||||||
|
serialize_error({bad_authenticator_config, Reason}) ->
|
||||||
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
|
message => binfmt("Bad authenticator config ~p", [Reason])}};
|
||||||
serialize_error(Reason) ->
|
serialize_error(Reason) ->
|
||||||
{400, #{code => <<"BAD_REQUEST">>,
|
{400, #{code => <<"BAD_REQUEST">>,
|
||||||
message => list_to_binary(io_lib:format("~p", [Reason]))}}.
|
message => binfmt("~p", [Reason])}}.
|
||||||
|
|
||||||
parse_position(<<"top">>) ->
|
parse_position(<<"top">>) ->
|
||||||
{ok, top};
|
{ok, top};
|
||||||
|
@ -2069,3 +2046,5 @@ to_atom(B) when is_binary(B) ->
|
||||||
binary_to_atom(B);
|
binary_to_atom(B);
|
||||||
to_atom(A) when is_atom(A) ->
|
to_atom(A) when is_atom(A) ->
|
||||||
A.
|
A.
|
||||||
|
|
||||||
|
binfmt(Fmt, Args) -> iolist_to_binary(io_lib:format(Fmt, Args)).
|
||||||
|
|
|
@ -438,7 +438,7 @@ read_certs(Source) -> Source.
|
||||||
|
|
||||||
maybe_write_certs(#{<<"ssl">> := #{<<"enable">> := true} = SSL} = Source) ->
|
maybe_write_certs(#{<<"ssl">> := #{<<"enable">> := true} = SSL} = Source) ->
|
||||||
Type = maps:get(<<"type">>, Source),
|
Type = maps:get(<<"type">>, Source),
|
||||||
emqx_tls_lib:ensure_ssl_files(filename:join(["authz", "certs", Type]), SSL);
|
emqx_tls_lib:ensure_ssl_files(filename:join(["authz", Type]), SSL);
|
||||||
maybe_write_certs(Source) -> Source.
|
maybe_write_certs(Source) -> Source.
|
||||||
|
|
||||||
write_file(Filename, Bytes0) ->
|
write_file(Filename, Bytes0) ->
|
||||||
|
|
|
@ -76,7 +76,7 @@ Path to the file which contains the ACL rules.<br>
|
||||||
If the file provisioned before starting EMQ X node, it can be placed anywhere
|
If the file provisioned before starting EMQ X node, it can be placed anywhere
|
||||||
as long as EMQ X has read access to it.
|
as long as EMQ X has read access to it.
|
||||||
In case rule set is created from EMQ X dashboard or management HTTP API,
|
In case rule set is created from EMQ X dashboard or management HTTP API,
|
||||||
the file will be placed in `authz` sub directory inside EMQ X's `data_dir`,
|
the file will be placed in `certs/authz` sub directory inside EMQ X's `data_dir`,
|
||||||
and the new rules will override all rules from the old config file.
|
and the new rules will override all rules from the old config file.
|
||||||
"""
|
"""
|
||||||
}}
|
}}
|
||||||
|
|
Loading…
Reference in New Issue