From ee178ccea9ec70c222a183031164e7b3e78e10da Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 14 Sep 2021 14:59:13 +0800 Subject: [PATCH] feat(upload certs): support return cert content by http api --- apps/emqx/src/emqx_authentication.erl | 94 ++++++++++---------- apps/emqx/test/emqx_authentication_SUITE.erl | 6 +- apps/emqx_authn/src/emqx_authn_api.erl | 24 +++-- 3 files changed, 68 insertions(+), 56 deletions(-) diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index 388f107e7..5b12ddb4f 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -198,7 +198,7 @@ pre_config_update(UpdateReq, OldConfig) -> end. do_pre_config_update({create_authenticator, _ChainName, Config}, OldConfig) -> - try convert_cert_options(Config) of + try convert_certs(Config) of NConfig -> {ok, OldConfig ++ [NConfig]} catch @@ -213,7 +213,7 @@ do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldCon do_pre_config_update({update_authenticator, _ChainName, AuthenticatorID, Config}, OldConfig) -> try lists:map(fun(OldConfig0) -> case AuthenticatorID =:= generate_id(OldConfig0) of - true -> convert_cert_options(Config, OldConfig0); + true -> convert_certs(Config, OldConfig0); false -> OldConfig0 end end, OldConfig) of @@ -616,42 +616,48 @@ reply(Reply, State) -> %% Internal functions %%------------------------------------------------------------------------------ -convert_cert_options(Config) -> - Keys = maps:keys(filter_empty(maps:with([<<"certfile">>, <<"keyfile">>, <<"cacertfile">>], Config))), - lists:foldl(fun(Key, Acc) -> - convert_cert_option(Key, Acc) - end, Config, Keys). +convert_certs(#{<<"ssl">> := SSLOpts} = Config) -> + NSSLOPts = lists:foldl(fun(K, Acc) -> + case maps:get(K, Acc, undefined) of + undefined -> Acc; + 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) -> - Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], - NewCerts = maps:with(Keys, NewConfig), - OldCerts = maps:fold(fun(K, V, Acc) -> - {ok, Bin} = file:read_file(V), - Acc#{K => Bin} - end, #{}, maps:with(Keys, OldConfig)), - Diff = diff_certs(NewCerts, OldCerts), - lists:foldl(fun({identical, K}, Acc) -> - Acc#{K => maps:get(K, OldConfig)}; - ({T, K}, Acc) when T =:= added orelse T =:= changed -> - convert_cert_option(K, Acc) - end, NewConfig, Diff). +convert_certs(#{<<"ssl">> := NewSSLOpts} = NewConfig, OldConfig) -> + OldSSLOpts = maps:get(<<"ssl">>, OldConfig, #{}), + Diff = diff_certs(NewSSLOpts, OldSSLOpts), + NSSLOpts = lists:foldl(fun({identical, K}, Acc) -> + Acc#{K => maps:get(K, OldSSLOpts)}; + ({_, K}, Acc) -> + CertFile = generate_filename(K), + ok = save_cert_to_file(CertFile, maps:get(K, NewSSLOpts)), + Acc#{K => CertFile} + end, NewSSLOpts, Diff), + NewConfig#{<<"ssl">> => NSSLOpts}; +convert_certs(NewConfig, _OldConfig) -> + NewConfig. -convert_cert_option(Key, Config) -> - PemBin = maps:get(Key, Config), +save_cert_to_file(Filename, PemBin) -> case public_key:pem_decode(PemBin) =/= [] of true -> - Filename = to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", generate_filename(Key)])), case filelib:ensure_dir(Filename) of ok -> case file:write_file(Filename, PemBin) of - ok -> Config#{Key => Filename}; - {error, Reason} -> error({convert_cert_option, {write_file, Reason}}) + ok -> ok; + {error, Reason} -> error({save_cert_to_file, {write_file, Reason}}) end; {error, Reason} -> - error({convert_cert_option, {ensure_dir, Reason}}) + error({save_cert_to_file, {ensure_dir, Reason}}) end; false -> - error({convert_cert_option, invalid_certificate}) + error({save_cert_to_file, invalid_certificate}) end. generate_filename(Key) -> @@ -660,31 +666,27 @@ generate_filename(Key) -> <<"certfile">> -> "cert-"; <<"cacertfile">> -> "cacert-" 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) -> - [I || I <- L, I =/= "" andalso I =/= undefined]; -filter_empty(M) when is_map(M) -> - maps:from_list(filter_empty(maps:to_list(M))). - -diff_certs(NewCerts0, OldCerts0) -> - NewCerts = filter_empty(NewCerts0), - OldCerts = filter_empty(OldCerts0), - Diff = lists:foldl(fun({OldK, OldPem}, Acc) -> - case maps:find(OldK, NewCerts) of - error -> - Acc; - {ok, NewPem} -> - case diff_cert(NewPem, OldPem) of +diff_certs(NewSSLOpts, OldSSLOpts) -> + Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], + CertPems = maps:with(Keys, NewSSLOpts), + CertFiles = maps:with(Keys, OldSSLOpts), + Diff = lists:foldl(fun({K, CertFile}, Acc) -> + case maps:find(K, CertPems) of + error -> Acc; + {ok, PemBin1} -> + {ok, PemBin2} = file:read_file(CertFile), + case diff_cert(PemBin1, PemBin2) of true -> - [{changed, OldK} | Acc]; + [{changed, K} | Acc]; false -> - [{identical, OldK} | Acc] + [{identical, K} | Acc] end end end, - [], maps:to_list(OldCerts)), - Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(OldCerts), NewCerts))], + [], maps:to_list(CertFiles)), + Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(CertFiles), CertPems))], Diff ++ Added. diff_cert(Pem1, Pem2) -> diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index 4af54f842..ff219e64c 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -252,13 +252,13 @@ t_convert_cert_options(_) -> , {<<"certfile">>, "cert.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))), Certs2 = certs([ {<<"keyfile">>, "key.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(maps:get(<<"keyfile">>, NCerts), maps:get(<<"keyfile">>, NCerts2)), ?assertEqual(maps:get(<<"certfile">>, NCerts), maps:get(<<"certfile">>, NCerts2)), @@ -267,7 +267,7 @@ t_convert_cert_options(_) -> , {<<"certfile">>, "client-cert.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))), ?assertNotEqual(maps:get(<<"keyfile">>, NCerts2), maps:get(<<"keyfile">>, NCerts3)), ?assertNotEqual(maps:get(<<"certfile">>, NCerts2), maps:get(<<"certfile">>, NCerts3)). diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 59897b51a..16f580d6a 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -1835,23 +1835,20 @@ find_listener(ListenerID) -> {ok, {Type, Name}} end. -% convert_tls_options(Config)-> - create_authenticator(ConfKeyPath, ChainName0, Config) -> ChainName = to_atom(ChainName0), - % {NConfig, Certs} = convert_tls_options(Config), case update_config(ConfKeyPath, {create_authenticator, ChainName, Config}) of {ok, #{post_config_update := #{?AUTHN := #{id := ID}}, raw_config := 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}} -> serialize_error(Reason) end. list_authenticators(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], {200, NAuthenticators}. @@ -1859,7 +1856,7 @@ list_authenticator(ConfKeyPath, AuthenticatorID) -> AuthenticatorsConfig = get_raw_config_with_defaults(ConfKeyPath), case find_config(AuthenticatorID, AuthenticatorsConfig) of {ok, AuthenticatorConfig} -> - {200, AuthenticatorConfig#{id => AuthenticatorID}}; + {200, maps:put(id, AuthenticatorID, convert_certs(AuthenticatorConfig))}; {error, Reason} -> serialize_error(Reason) end. @@ -1870,7 +1867,7 @@ update_authenticator(ConfKeyPath, ChainName0, AuthenticatorID, Config) -> {ok, #{post_config_update := #{?AUTHN := #{id := ID}}, raw_config := 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}} -> serialize_error(Reason) end. @@ -1974,6 +1971,19 @@ fill_defaults(Config) -> ?AUTHN, #{<<"authentication">> => Config}, #{nullable => true, no_conversion => true}), 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}}) -> {404, #{code => <<"NOT_FOUND">>, message => list_to_binary(