feat(upload certs): support return cert content by http api

This commit is contained in:
zhouzb 2021-09-14 14:59:13 +08:00
parent 63d3a7b525
commit ee178ccea9
3 changed files with 68 additions and 56 deletions

View File

@ -198,7 +198,7 @@ pre_config_update(UpdateReq, OldConfig) ->
end. end.
do_pre_config_update({create_authenticator, _ChainName, Config}, OldConfig) -> do_pre_config_update({create_authenticator, _ChainName, Config}, OldConfig) ->
try convert_cert_options(Config) of try convert_certs(Config) of
NConfig -> NConfig ->
{ok, OldConfig ++ [NConfig]} {ok, OldConfig ++ [NConfig]}
catch catch
@ -213,7 +213,7 @@ do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldCon
do_pre_config_update({update_authenticator, _ChainName, AuthenticatorID, Config}, OldConfig) -> do_pre_config_update({update_authenticator, _ChainName, AuthenticatorID, Config}, OldConfig) ->
try lists:map(fun(OldConfig0) -> try lists:map(fun(OldConfig0) ->
case AuthenticatorID =:= generate_id(OldConfig0) of case AuthenticatorID =:= generate_id(OldConfig0) of
true -> convert_cert_options(Config, OldConfig0); true -> convert_certs(Config, OldConfig0);
false -> OldConfig0 false -> OldConfig0
end end
end, OldConfig) of end, OldConfig) of
@ -616,42 +616,48 @@ reply(Reply, State) ->
%% Internal functions %% Internal functions
%%------------------------------------------------------------------------------ %%------------------------------------------------------------------------------
convert_cert_options(Config) -> convert_certs(#{<<"ssl">> := SSLOpts} = Config) ->
Keys = maps:keys(filter_empty(maps:with([<<"certfile">>, <<"keyfile">>, <<"cacertfile">>], Config))), NSSLOPts = lists:foldl(fun(K, Acc) ->
lists:foldl(fun(Key, Acc) -> case maps:get(K, Acc, undefined) of
convert_cert_option(Key, Acc) undefined -> Acc;
end, Config, Keys). PemBin ->
CertFile = generate_filename(K),
ok = save_cert_to_file(CertFile, PemBin),
Acc#{K => CertFile}
end
end, SSLOpts, [<<"certfile">>, <<"keyfile">>, <<"cacertfile">>]),
Config#{<<"ssl">> => NSSLOPts};
convert_certs(Config) ->
Config.
convert_cert_options(NewConfig, OldConfig) -> convert_certs(#{<<"ssl">> := NewSSLOpts} = NewConfig, OldConfig) ->
Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], OldSSLOpts = maps:get(<<"ssl">>, OldConfig, #{}),
NewCerts = maps:with(Keys, NewConfig), Diff = diff_certs(NewSSLOpts, OldSSLOpts),
OldCerts = maps:fold(fun(K, V, Acc) -> NSSLOpts = lists:foldl(fun({identical, K}, Acc) ->
{ok, Bin} = file:read_file(V), Acc#{K => maps:get(K, OldSSLOpts)};
Acc#{K => Bin} ({_, K}, Acc) ->
end, #{}, maps:with(Keys, OldConfig)), CertFile = generate_filename(K),
Diff = diff_certs(NewCerts, OldCerts), ok = save_cert_to_file(CertFile, maps:get(K, NewSSLOpts)),
lists:foldl(fun({identical, K}, Acc) -> Acc#{K => CertFile}
Acc#{K => maps:get(K, OldConfig)}; end, NewSSLOpts, Diff),
({T, K}, Acc) when T =:= added orelse T =:= changed -> NewConfig#{<<"ssl">> => NSSLOpts};
convert_cert_option(K, Acc) convert_certs(NewConfig, _OldConfig) ->
end, NewConfig, Diff). NewConfig.
convert_cert_option(Key, Config) -> save_cert_to_file(Filename, PemBin) ->
PemBin = maps:get(Key, Config),
case public_key:pem_decode(PemBin) =/= [] of case public_key:pem_decode(PemBin) =/= [] of
true -> true ->
Filename = to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", generate_filename(Key)])),
case filelib:ensure_dir(Filename) of case filelib:ensure_dir(Filename) of
ok -> ok ->
case file:write_file(Filename, PemBin) of case file:write_file(Filename, PemBin) of
ok -> Config#{Key => Filename}; ok -> ok;
{error, Reason} -> error({convert_cert_option, {write_file, Reason}}) {error, Reason} -> error({save_cert_to_file, {write_file, Reason}})
end; end;
{error, Reason} -> {error, Reason} ->
error({convert_cert_option, {ensure_dir, Reason}}) error({save_cert_to_file, {ensure_dir, Reason}})
end; end;
false -> false ->
error({convert_cert_option, invalid_certificate}) error({save_cert_to_file, invalid_certificate})
end. end.
generate_filename(Key) -> generate_filename(Key) ->
@ -660,31 +666,27 @@ generate_filename(Key) ->
<<"certfile">> -> "cert-"; <<"certfile">> -> "cert-";
<<"cacertfile">> -> "cacert-" <<"cacertfile">> -> "cacert-"
end, end,
Prefix ++ emqx_plugin_libs_id:gen() ++ ".pem". to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", Prefix ++ emqx_plugin_libs_id:gen() ++ ".pem"])).
filter_empty(L) when is_list(L) -> diff_certs(NewSSLOpts, OldSSLOpts) ->
[I || I <- L, I =/= "" andalso I =/= undefined]; Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>],
filter_empty(M) when is_map(M) -> CertPems = maps:with(Keys, NewSSLOpts),
maps:from_list(filter_empty(maps:to_list(M))). CertFiles = maps:with(Keys, OldSSLOpts),
Diff = lists:foldl(fun({K, CertFile}, Acc) ->
diff_certs(NewCerts0, OldCerts0) -> case maps:find(K, CertPems) of
NewCerts = filter_empty(NewCerts0), error -> Acc;
OldCerts = filter_empty(OldCerts0), {ok, PemBin1} ->
Diff = lists:foldl(fun({OldK, OldPem}, Acc) -> {ok, PemBin2} = file:read_file(CertFile),
case maps:find(OldK, NewCerts) of case diff_cert(PemBin1, PemBin2) of
error ->
Acc;
{ok, NewPem} ->
case diff_cert(NewPem, OldPem) of
true -> true ->
[{changed, OldK} | Acc]; [{changed, K} | Acc];
false -> false ->
[{identical, OldK} | Acc] [{identical, K} | Acc]
end end
end end
end, end,
[], maps:to_list(OldCerts)), [], maps:to_list(CertFiles)),
Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(OldCerts), NewCerts))], Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(CertFiles), CertPems))],
Diff ++ Added. Diff ++ Added.
diff_cert(Pem1, Pem2) -> diff_cert(Pem1, Pem2) ->

View File

@ -252,13 +252,13 @@ t_convert_cert_options(_) ->
, {<<"certfile">>, "cert.pem"} , {<<"certfile">>, "cert.pem"}
, {<<"cacertfile">>, "cacert.pem"} , {<<"cacertfile">>, "cacert.pem"}
]), ]),
NCerts = ?AUTHN:convert_cert_options(Certs), #{<<"ssl">> := NCerts} = ?AUTHN:convert_certs(#{<<"ssl">> => Certs}),
?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, Certs))), ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, Certs))),
Certs2 = certs([ {<<"keyfile">>, "key.pem"} Certs2 = certs([ {<<"keyfile">>, "key.pem"}
, {<<"certfile">>, "cert.pem"} , {<<"certfile">>, "cert.pem"}
]), ]),
NCerts2 = ?AUTHN:convert_cert_options(Certs2, NCerts), #{<<"ssl">> := NCerts2} = ?AUTHN:convert_certs(#{<<"ssl">> => Certs2}, #{<<"ssl">> => NCerts}),
?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, Certs2))), ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, Certs2))),
?assertEqual(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, NCerts2)), ?assertEqual(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, NCerts2)),
?assertEqual(maps:get(<<"certfile">>, NCerts), maps:get(<<"certfile">>, NCerts2)), ?assertEqual(maps:get(<<"certfile">>, NCerts), maps:get(<<"certfile">>, NCerts2)),
@ -267,7 +267,7 @@ t_convert_cert_options(_) ->
, {<<"certfile">>, "client-cert.pem"} , {<<"certfile">>, "client-cert.pem"}
, {<<"cacertfile">>, "cacert.pem"} , {<<"cacertfile">>, "cacert.pem"}
]), ]),
NCerts3 = ?AUTHN:convert_cert_options(Certs3, NCerts2), #{<<"ssl">> := NCerts3} = ?AUTHN:convert_certs(#{<<"ssl">> => Certs3}, #{<<"ssl">> => NCerts2}),
?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts3), maps:get(<<"keyfile">>, Certs3))), ?assertEqual(false, diff_cert(maps:get(<<"keyfile">>, NCerts3), maps:get(<<"keyfile">>, Certs3))),
?assertNotEqual(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, NCerts3)), ?assertNotEqual(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, NCerts3)),
?assertNotEqual(maps:get(<<"certfile">>, NCerts2), maps:get(<<"certfile">>, NCerts3)). ?assertNotEqual(maps:get(<<"certfile">>, NCerts2), maps:get(<<"certfile">>, NCerts3)).

View File

@ -1835,23 +1835,20 @@ find_listener(ListenerID) ->
{ok, {Type, Name}} {ok, {Type, Name}}
end. end.
% convert_tls_options(Config)->
create_authenticator(ConfKeyPath, ChainName0, Config) -> create_authenticator(ConfKeyPath, ChainName0, Config) ->
ChainName = to_atom(ChainName0), ChainName = to_atom(ChainName0),
% {NConfig, Certs} = convert_tls_options(Config),
case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of
{ok, #{post_config_update := #{?AUTHN := #{id := ID}}, {ok, #{post_config_update := #{?AUTHN := #{id := ID}},
raw_config := AuthenticatorsConfig}} -> raw_config := AuthenticatorsConfig}} ->
{ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig),
{200, maps:put(id, ID, fill_defaults(AuthenticatorConfig))}; {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))};
{error, {_, _, Reason}} -> {error, {_, _, Reason}} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
list_authenticators(ConfKeyPath) -> list_authenticators(ConfKeyPath) ->
AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath), AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath),
NAuthenticators = [maps:put(id, ?AUTHN:generate_id(AuthenticatorConfig), AuthenticatorConfig) NAuthenticators = [maps:put(id, ?AUTHN:generate_id(AuthenticatorConfig), convert_certs(AuthenticatorConfig))
|| AuthenticatorConfig <- AuthenticatorsConfig], || AuthenticatorConfig <- AuthenticatorsConfig],
{200, NAuthenticators}. {200, NAuthenticators}.
@ -1859,7 +1856,7 @@ list_authenticator(ConfKeyPath, AuthenticatorID) ->
AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath), AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath),
case find_config(AuthenticatorID, AuthenticatorsConfig) of case find_config(AuthenticatorID, AuthenticatorsConfig) of
{ok, AuthenticatorConfig} -> {ok, AuthenticatorConfig} ->
{200, AuthenticatorConfig#{id => AuthenticatorID}}; {200, maps:put(id, AuthenticatorID, convert_certs(AuthenticatorConfig))};
{error, Reason} -> {error, Reason} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
@ -1870,7 +1867,7 @@ update_authenticator(ConfKeyPath, ChainName0, AuthenticatorID, Config) ->
{ok, #{post_config_update := #{?AUTHN := #{id := ID}}, {ok, #{post_config_update := #{?AUTHN := #{id := ID}},
raw_config := AuthenticatorsConfig}} -> raw_config := AuthenticatorsConfig}} ->
{ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig), {ok, AuthenticatorConfig} = find_config(ID, AuthenticatorsConfig),
{200, maps:put(id, ID, fill_defaults(AuthenticatorConfig))}; {200, maps:put(id, ID, convert_certs(fill_defaults(AuthenticatorConfig)))};
{error, {_, _, Reason}} -> {error, {_, _, Reason}} ->
serialize_error(Reason) serialize_error(Reason)
end. end.
@ -1974,6 +1971,19 @@ fill_defaults(Config) ->
?AUTHN, #{<<"authentication">> => Config}, #{nullable => true, no_conversion => true}), ?AUTHN, #{<<"authentication">> => Config}, #{nullable => true, no_conversion => true}),
CheckedConfig. CheckedConfig.
convert_certs(#{<<"ssl">> := SSLOpts} = Config) ->
NSSLOpts = lists:foldl(fun(K, Acc) ->
case maps:get(K, Acc, undefined) of
undefined -> Acc;
Filename ->
{ok, Bin} = file:read_file(Filename),
Acc#{K => Bin}
end
end, SSLOpts, [<<"certfile">>, <<"keyfile">>, <<"cacertfile">>]),
Config#{<<"ssl">> => NSSLOpts};
convert_certs(Config) ->
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 => list_to_binary(