From 06861e377f41fc1c44d5bf479e0b60efd5fe084f Mon Sep 17 00:00:00 2001 From: Andrew Mayorov Date: Tue, 7 Nov 2023 23:53:10 +0700 Subject: [PATCH] feat(ldap): accept wrapped secrets as passwords --- .../src/emqx_dashboard_sso_api.erl | 10 ++++---- .../test/emqx_dashboard_sso_ldap_SUITE.erl | 18 ++++++++++++--- apps/emqx_ldap/src/emqx_ldap.erl | 23 +++++++++---------- 3 files changed, 31 insertions(+), 20 deletions(-) diff --git a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl index c023ace51..830b50676 100644 --- a/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl +++ b/apps/emqx_dashboard_sso/src/emqx_dashboard_sso_api.erl @@ -204,7 +204,7 @@ backend(get, #{bindings := #{backend := Type}}) -> undefined -> {404, #{code => ?BACKEND_NOT_FOUND, message => <<"Backend not found">>}}; Backend -> - {200, to_json(Backend)} + {200, to_redacted_json(Backend)} end; backend(put, #{bindings := #{backend := Backend}, body := Config}) -> ?SLOG(info, #{ @@ -264,9 +264,9 @@ valid_config(_, _, _) -> {error, invalid_config}. handle_backend_update_result({ok, #{backend := saml} = State}, _Config) -> - {200, to_json(maps:without([idp_meta, sp], State))}; + {200, to_redacted_json(maps:without([idp_meta, sp], State))}; handle_backend_update_result({ok, _State}, Config) -> - {200, to_json(Config)}; + {200, to_redacted_json(Config)}; handle_backend_update_result(ok, _) -> 204; handle_backend_update_result({error, not_exists}, _) -> @@ -278,9 +278,9 @@ handle_backend_update_result({error, Reason}, _) when is_binary(Reason) -> handle_backend_update_result({error, Reason}, _) -> {400, #{code => ?BAD_REQUEST, message => emqx_dashboard_sso:format(["Reason: ", Reason])}}. -to_json(Data) -> +to_redacted_json(Data) -> emqx_utils_maps:jsonable_map( - Data, + emqx_utils:redact(Data), fun(K, V) -> {K, emqx_utils_maps:binary_string(V)} end diff --git a/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl b/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl index 8966ffca9..9e831b4d2 100644 --- a/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl +++ b/apps/emqx_dashboard_sso/test/emqx_dashboard_sso_ldap_SUITE.erl @@ -10,9 +10,11 @@ -include_lib("emqx_dashboard/include/emqx_dashboard.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). -include_lib("eunit/include/eunit.hrl"). +-include_lib("common_test/include/ct.hrl"). -define(LDAP_HOST, "ldap"). -define(LDAP_DEFAULT_PORT, 389). +-define(LDAP_PASSWORD, <<"public">>). -define(LDAP_USER, <<"viewer1">>). -define(LDAP_USER_PASSWORD, <<"viewer1">>). -define(LDAP_BASE_DN, <<"ou=dashboard,dc=emqx,dc=io">>). @@ -128,9 +130,19 @@ t_update({init, Config}) -> Config; t_update({'end', _Config}) -> ok; -t_update(_) -> +t_update(Config) -> Path = uri(["sso", "ldap"]), - {ok, 200, Result} = request(put, Path, ldap_config(#{<<"enable">> => <<"true">>})), + %% NOTE: this time verify that supplying password through file-based secret works. + PasswordFilename = filename:join([?config(priv_dir, Config), "passfile"]), + ok = file:write_file(PasswordFilename, ?LDAP_PASSWORD), + {ok, 200, Result} = request( + put, + Path, + ldap_config(#{ + <<"enable">> => <<"true">>, + <<"password">> => iolist_to_binary(["file://", PasswordFilename]) + }) + ), check_running([<<"ldap">>]), ?assertMatch(#{backend := <<"ldap">>, enable := true}, decode_json(Result)), ?assertMatch([#{backend := <<"ldap">>, enable := true}], get_sso()), @@ -287,7 +299,7 @@ ldap_config(Override) -> <<"base_dn">> => ?LDAP_BASE_DN, <<"filter">> => ?LDAP_FILTER_WITH_UID, <<"username">> => <<"cn=root,dc=emqx,dc=io">>, - <<"password">> => <<"public">>, + <<"password">> => ?LDAP_PASSWORD, <<"pool_size">> => 8 }, Override diff --git a/apps/emqx_ldap/src/emqx_ldap.erl b/apps/emqx_ldap/src/emqx_ldap.erl index a77a8ecf0..315733b79 100644 --- a/apps/emqx_ldap/src/emqx_ldap.erl +++ b/apps/emqx_ldap/src/emqx_ldap.erl @@ -53,8 +53,6 @@ filter_tokens := params_tokens() }. --define(ECS, emqx_connector_schema_lib). - %%===================================================================== %% Hocon schema roots() -> @@ -63,9 +61,9 @@ roots() -> fields(config) -> [ {server, server()}, - {pool_size, fun ?ECS:pool_size/1}, + {pool_size, fun emqx_connector_schema_lib:pool_size/1}, {username, fun ensure_username/1}, - {password, fun ?ECS:password/1}, + {password, emqx_connector_schema_lib:password_field()}, {base_dn, ?HOCON(binary(), #{ desc => ?DESC(base_dn), @@ -124,7 +122,7 @@ server() -> ensure_username(required) -> true; ensure_username(Field) -> - ?ECS:username(Field). + emqx_connector_schema_lib:username(Field). %% =================================================================== callback_mode() -> always_sync. @@ -223,7 +221,8 @@ connect(Options) -> OpenOpts = maps:to_list(maps:with([port, sslopts], Conf)), case eldap:open([Host], [{log, fun log/3}, {timeout, RequestTimeout} | OpenOpts]) of {ok, Handle} = Ret -> - case eldap:simple_bind(Handle, Username, Password) of + %% TODO: teach `eldap` to accept 0-arity closures as passwords. + case eldap:simple_bind(Handle, Username, emqx_secret:unwrap(Password)) of ok -> Ret; Error -> Error end; @@ -320,13 +319,13 @@ log(Level, Format, Args) -> ). prepare_template(Config, State) -> - do_prepare_template(maps:to_list(maps:with([base_dn, filter], Config)), State). + maps:fold(fun prepare_template/3, State, Config). -do_prepare_template([{base_dn, V} | T], State) -> - do_prepare_template(T, State#{base_tokens => emqx_placeholder:preproc_tmpl(V)}); -do_prepare_template([{filter, V} | T], State) -> - do_prepare_template(T, State#{filter_tokens => emqx_placeholder:preproc_tmpl(V)}); -do_prepare_template([], State) -> +prepare_template(base_dn, V, State) -> + State#{base_tokens => emqx_placeholder:preproc_tmpl(V)}; +prepare_template(filter, V, State) -> + State#{filter_tokens => emqx_placeholder:preproc_tmpl(V)}; +prepare_template(_Entry, _, State) -> State. filter_escape(Binary) when is_binary(Binary) ->