From 63d3a7b52506d5462275be6fb8f51d41dbca019e Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 14 Sep 2021 13:38:09 +0800 Subject: [PATCH 1/5] feat(upload certs): save certs to file --- apps/emqx/src/emqx_authentication.erl | 106 +++++++++++++++++- apps/emqx/test/emqx_authentication_SUITE.erl | 53 ++++++++- apps/emqx_authn/src/emqx_authn_api.erl | 3 + .../src/emqx_connector_schema_lib.erl | 6 +- 4 files changed, 158 insertions(+), 10 deletions(-) diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index 2e53d85eb..388f107e7 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -73,6 +73,11 @@ , code_change/3 ]). +-ifdef(TEST). +-compile(export_all). +-compile(nowarn_export_all). +-endif. + -define(CHAINS_TAB, emqx_authn_chains). -define(VER_1, <<"1">>). @@ -193,20 +198,31 @@ pre_config_update(UpdateReq, OldConfig) -> end. do_pre_config_update({create_authenticator, _ChainName, Config}, OldConfig) -> - {ok, OldConfig ++ [Config]}; + try convert_cert_options(Config) of + NConfig -> + {ok, OldConfig ++ [NConfig]} + catch + error:{convert_cert_option, _} = Reason -> + {error, Reason} + end; do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) -> NewConfig = lists:filter(fun(OldConfig0) -> AuthenticatorID =/= generate_id(OldConfig0) end, OldConfig), {ok, NewConfig}; do_pre_config_update({update_authenticator, _ChainName, AuthenticatorID, Config}, OldConfig) -> - NewConfig = lists:map(fun(OldConfig0) -> + try lists:map(fun(OldConfig0) -> case AuthenticatorID =:= generate_id(OldConfig0) of - true -> maps:merge(OldConfig0, Config); + true -> convert_cert_options(Config, OldConfig0); false -> OldConfig0 end - end, OldConfig), - {ok, NewConfig}; + end, OldConfig) of + NewConfig -> + {ok, NewConfig} + catch + error:{convert_cert_option, _} = Reason -> + {error, Reason} + end; do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) -> case split_by_id(AuthenticatorID, OldConfig) of {error, Reason} -> {error, Reason}; @@ -600,6 +616,83 @@ 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_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_cert_option(Key, Config) -> + PemBin = maps:get(Key, Config), + 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}}) + end; + {error, Reason} -> + error({convert_cert_option, {ensure_dir, Reason}}) + end; + false -> + error({convert_cert_option, invalid_certificate}) + end. + +generate_filename(Key) -> + Prefix = case Key of + <<"keyfile">> -> "key-"; + <<"certfile">> -> "cert-"; + <<"cacertfile">> -> "cacert-" + end, + 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 + true -> + [{changed, OldK} | Acc]; + false -> + [{identical, OldK} | Acc] + end + end + end, + [], maps:to_list(OldCerts)), + Added = [{added, K} || K <- maps:keys(maps:without(maps:keys(OldCerts), NewCerts))], + Diff ++ Added. + +diff_cert(Pem1, Pem2) -> + cal_md5_for_cert(Pem1) =/= cal_md5_for_cert(Pem2). + +cal_md5_for_cert(Pem) -> + crypto:hash(md5, term_to_binary(public_key:pem_decode(Pem))). + split_by_id(ID, AuthenticatorsConfig) -> case lists:foldl( fun(C, {P1, P2, F0}) -> @@ -777,3 +870,6 @@ to_list(M) when is_map(M) -> [M]; to_list(L) when is_list(L) -> L. + +to_bin(B) when is_binary(B) -> B; +to_bin(L) when is_list(L) -> list_to_binary(L). diff --git a/apps/emqx/test/emqx_authentication_SUITE.erl b/apps/emqx/test/emqx_authentication_SUITE.erl index 001a4b40e..4af54f842 100644 --- a/apps/emqx/test/emqx_authentication_SUITE.erl +++ b/apps/emqx/test/emqx_authentication_SUITE.erl @@ -92,6 +92,19 @@ end_per_suite(_) -> emqx_ct_helpers:stop_apps([]), ok. +init_per_testcase(_, Config) -> + meck:new(emqx, [non_strict, passthrough, no_history, no_link]), + meck:expect(emqx, get_config, fun([node, data_dir]) -> + {data_dir, Data} = lists:keyfind(data_dir, 1, Config), + Data; + (C) -> meck:passthrough([C]) + end), + Config. + +end_per_testcase(_, _Config) -> + meck:unload(emqx), + ok. + t_chain(_) -> % CRUD of authentication chain ChainName = 'test', @@ -203,7 +216,7 @@ t_update_config(_) -> ?assertMatch({ok, _}, update_config([authentication], {create_authenticator, Global, AuthenticatorConfig2})), ?assertMatch({ok, #{id := ID2, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(Global, ID2)), - ?assertMatch({ok, _}, update_config([authentication], {update_authenticator, Global, ID1, #{}})), + ?assertMatch({ok, _}, update_config([authentication], {update_authenticator, Global, ID1, AuthenticatorConfig1#{enable => false}})), ?assertMatch({ok, #{id := ID1, state := #{mark := 2}}}, ?AUTHN:lookup_authenticator(Global, ID1)), ?assertMatch({ok, _}, update_config([authentication], {move_authenticator, Global, ID2, top})), @@ -220,7 +233,7 @@ t_update_config(_) -> ?assertMatch({ok, _}, update_config(ConfKeyPath, {create_authenticator, ListenerID, AuthenticatorConfig2})), ?assertMatch({ok, #{id := ID2, state := #{mark := 1}}}, ?AUTHN:lookup_authenticator(ListenerID, ID2)), - ?assertMatch({ok, _}, update_config(ConfKeyPath, {update_authenticator, ListenerID, ID1, #{}})), + ?assertMatch({ok, _}, update_config(ConfKeyPath, {update_authenticator, ListenerID, ID1, AuthenticatorConfig1#{enable => false}})), ?assertMatch({ok, #{id := ID1, state := #{mark := 2}}}, ?AUTHN:lookup_authenticator(ListenerID, ID1)), ?assertMatch({ok, _}, update_config(ConfKeyPath, {move_authenticator, ListenerID, ID2, top})), @@ -234,5 +247,41 @@ t_update_config(_) -> ?AUTHN:remove_provider(AuthNType2), ok. +t_convert_cert_options(_) -> + Certs = certs([ {<<"keyfile">>, "key.pem"} + , {<<"certfile">>, "cert.pem"} + , {<<"cacertfile">>, "cacert.pem"} + ]), + NCerts = ?AUTHN:convert_cert_options(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), + ?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)), + + Certs3 = certs([ {<<"keyfile">>, "client-key.pem"} + , {<<"certfile">>, "client-cert.pem"} + , {<<"cacertfile">>, "cacert.pem"} + ]), + NCerts3 = ?AUTHN:convert_cert_options(Certs3, 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)). + update_config(Path, ConfigRequest) -> emqx:update_config(Path, ConfigRequest, #{rawconf_with_defaults => true}). + +certs(Certs) -> + CertsPath = emqx_ct_helpers:deps_path(emqx, "etc/certs"), + lists:foldl(fun({Key, Filename}, Acc) -> + {ok, Bin} = file:read_file(filename:join([CertsPath, Filename])), + Acc#{Key => Bin} + end, #{}, Certs). + +diff_cert(CertFile, CertPem2) -> + {ok, CertPem1} = file:read_file(CertFile), + ?AUTHN:diff_cert(CertPem1, CertPem2). \ No newline at end of file diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index e492100ee..59897b51a 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -1835,8 +1835,11 @@ 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}} -> diff --git a/apps/emqx_connector/src/emqx_connector_schema_lib.erl b/apps/emqx_connector/src/emqx_connector_schema_lib.erl index 6dcc564af..ecdfb1416 100644 --- a/apps/emqx_connector/src/emqx_connector_schema_lib.erl +++ b/apps/emqx_connector/src/emqx_connector_schema_lib.erl @@ -107,15 +107,15 @@ auto_reconnect(default) -> true; auto_reconnect(_) -> undefined. cacertfile(type) -> string(); -cacertfile(default) -> ""; +cacertfile(nullable) -> true; cacertfile(_) -> undefined. keyfile(type) -> string(); -keyfile(default) -> ""; +keyfile(nullable) -> true; keyfile(_) -> undefined. certfile(type) -> string(); -certfile(default) -> ""; +certfile(nullable) -> true; certfile(_) -> undefined. verify(type) -> boolean(); From ee178ccea9ec70c222a183031164e7b3e78e10da Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 14 Sep 2021 14:59:13 +0800 Subject: [PATCH 2/5] 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( From 1a61640b15f995b3cdc5369d4e5a436d5739d22a Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 14 Sep 2021 15:07:57 +0800 Subject: [PATCH 3/5] feat(upload certs): serialize errors about saving cert --- apps/emqx/src/emqx_authentication.erl | 4 ++-- apps/emqx_authn/src/emqx_authn_api.erl | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index 5b12ddb4f..c35a7ea1b 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -202,7 +202,7 @@ do_pre_config_update({create_authenticator, _ChainName, Config}, OldConfig) -> NConfig -> {ok, OldConfig ++ [NConfig]} catch - error:{convert_cert_option, _} = Reason -> + error:{save_cert_to_file, _} = Reason -> {error, Reason} end; do_pre_config_update({delete_authenticator, _ChainName, AuthenticatorID}, OldConfig) -> @@ -220,7 +220,7 @@ do_pre_config_update({update_authenticator, _ChainName, AuthenticatorID, Config} NewConfig -> {ok, NewConfig} catch - error:{convert_cert_option, _} = Reason -> + error:{save_cert_to_file, _} = Reason -> {error, Reason} end; do_pre_config_update({move_authenticator, _ChainName, AuthenticatorID, Position}, OldConfig) -> diff --git a/apps/emqx_authn/src/emqx_authn_api.erl b/apps/emqx_authn/src/emqx_authn_api.erl index 16f580d6a..93e8c4746 100644 --- a/apps/emqx_authn/src/emqx_authn_api.erl +++ b/apps/emqx_authn/src/emqx_authn_api.erl @@ -2024,6 +2024,16 @@ serialize_error(unsupported_operation) -> {400, #{code => <<"BAD_REQUEST">>, message => <<"Operation not supported in this authentication type">>}}; +serialize_error({save_cert_to_file, invalid_certificate}) -> + {400, #{code => <<"BAD_REQUEST">>, + message => <<"Invalid certificate">>}}; + +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">>, message => list_to_binary( From f6d7739f01ec1271f11741ad6e5dd4cb210e8e83 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Tue, 14 Sep 2021 16:09:36 +0800 Subject: [PATCH 4/5] fix(upload certs): fix external dependency --- apps/emqx/src/emqx_authentication.erl | 2 +- apps/emqx/src/emqx_misc.erl | 37 +++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index c35a7ea1b..d4ab4cdf3 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -666,7 +666,7 @@ generate_filename(Key) -> <<"certfile">> -> "cert-"; <<"cacertfile">> -> "cacert-" end, - to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", Prefix ++ emqx_plugin_libs_id:gen() ++ ".pem"])). + to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", Prefix ++ emqx_misc:gen() ++ ".pem"])). diff_certs(NewSSLOpts, OldSSLOpts) -> Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], diff --git a/apps/emqx/src/emqx_misc.erl b/apps/emqx/src/emqx_misc.erl index d45b6f7ce..ce98a3066 100644 --- a/apps/emqx/src/emqx_misc.erl +++ b/apps/emqx/src/emqx_misc.erl @@ -45,6 +45,8 @@ , index_of/2 , maybe_parse_ip/1 , ipv6_probe/1 + , gen/0 + , gen/1 ]). -export([ bin2hexstr_A_F/1 @@ -52,6 +54,8 @@ , hexstr2bin/1 ]). +-define(SHORT, 8). + %% @doc Parse v4 or v6 string format address to tuple. %% `Host' itself is returned if it's not an ip string. maybe_parse_ip(Host) -> @@ -298,6 +302,39 @@ hexchar2int(I) when I >= $0 andalso I =< $9 -> I - $0; hexchar2int(I) when I >= $A andalso I =< $F -> I - $A + 10; hexchar2int(I) when I >= $a andalso I =< $f -> I - $a + 10. +-spec(gen() -> list()). +gen() -> + gen(?SHORT). + +-spec(gen(integer()) -> list()). +gen(Len) -> + BitLen = Len * 4, + <> = crypto:strong_rand_bytes(Len div 2), + int_to_hex(R, Len). + +%%------------------------------------------------------------------------------ +%% Internal Functions +%%------------------------------------------------------------------------------ + +int_to_hex(I, N) when is_integer(I), I >= 0 -> + int_to_hex([], I, 1, N). + +int_to_hex(L, I, Count, N) + when I < 16 -> + pad([int_to_hex(I) | L], N - Count); +int_to_hex(L, I, Count, N) -> + int_to_hex([int_to_hex(I rem 16) | L], I div 16, Count + 1, N). + +int_to_hex(I) when 0 =< I, I =< 9 -> + I + $0; +int_to_hex(I) when 10 =< I, I =< 15 -> + (I - 10) + $a. + +pad(L, 0) -> + L; +pad(L, Count) -> + pad([$0 | L], Count - 1). + -ifdef(TEST). -include_lib("eunit/include/eunit.hrl"). From 0fe300575e7c521deabaf1ab3133aeb1ce40c898 Mon Sep 17 00:00:00 2001 From: zhouzb Date: Wed, 15 Sep 2021 09:59:35 +0800 Subject: [PATCH 5/5] chore(authn): better function name --- apps/emqx/src/emqx_authentication.erl | 2 +- apps/emqx/src/emqx_misc.erl | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/apps/emqx/src/emqx_authentication.erl b/apps/emqx/src/emqx_authentication.erl index d4ab4cdf3..483305aa4 100644 --- a/apps/emqx/src/emqx_authentication.erl +++ b/apps/emqx/src/emqx_authentication.erl @@ -666,7 +666,7 @@ generate_filename(Key) -> <<"certfile">> -> "cert-"; <<"cacertfile">> -> "cacert-" end, - to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", Prefix ++ emqx_misc:gen() ++ ".pem"])). + to_bin(filename:join([emqx:get_config([node, data_dir]), "certs/authn", Prefix ++ emqx_misc:gen_id() ++ ".pem"])). diff_certs(NewSSLOpts, OldSSLOpts) -> Keys = [<<"cacertfile">>, <<"certfile">>, <<"keyfile">>], diff --git a/apps/emqx/src/emqx_misc.erl b/apps/emqx/src/emqx_misc.erl index ce98a3066..446039778 100644 --- a/apps/emqx/src/emqx_misc.erl +++ b/apps/emqx/src/emqx_misc.erl @@ -45,8 +45,8 @@ , index_of/2 , maybe_parse_ip/1 , ipv6_probe/1 - , gen/0 - , gen/1 + , gen_id/0 + , gen_id/1 ]). -export([ bin2hexstr_A_F/1 @@ -302,12 +302,12 @@ hexchar2int(I) when I >= $0 andalso I =< $9 -> I - $0; hexchar2int(I) when I >= $A andalso I =< $F -> I - $A + 10; hexchar2int(I) when I >= $a andalso I =< $f -> I - $a + 10. --spec(gen() -> list()). -gen() -> - gen(?SHORT). +-spec(gen_id() -> list()). +gen_id() -> + gen_id(?SHORT). --spec(gen(integer()) -> list()). -gen(Len) -> +-spec(gen_id(integer()) -> list()). +gen_id(Len) -> BitLen = Len * 4, <> = crypto:strong_rand_bytes(Len div 2), int_to_hex(R, Len).