diff --git a/apps/emqx/etc/emqx.conf b/apps/emqx/etc/emqx.conf index ee345e9d6..e69de29bb 100644 --- a/apps/emqx/etc/emqx.conf +++ b/apps/emqx/etc/emqx.conf @@ -1,43 +0,0 @@ -listeners.tcp.default { - bind = "0.0.0.0:1883" - max_connections = 1024000 -} - -listeners.ssl.default { - bind = "0.0.0.0:8883" - max_connections = 512000 - ssl_options { - keyfile = "{{ platform_etc_dir }}/certs/key.pem" - certfile = "{{ platform_etc_dir }}/certs/cert.pem" - cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" - } -} - -listeners.ws.default { - bind = "0.0.0.0:8083" - max_connections = 1024000 - websocket.mqtt_path = "/mqtt" -} - -listeners.wss.default { - bind = "0.0.0.0:8084" - max_connections = 512000 - websocket.mqtt_path = "/mqtt" - ssl_options { - keyfile = "{{ platform_etc_dir }}/certs/key.pem" - certfile = "{{ platform_etc_dir }}/certs/cert.pem" - cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" - } -} - -# listeners.quic.default { -# enabled = true -# bind = "0.0.0.0:14567" -# max_connections = 1024000 -# ssl_options { -# verify = verify_none -# keyfile = "{{ platform_etc_dir }}/certs/key.pem" -# certfile = "{{ platform_etc_dir }}/certs/cert.pem" -# cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem" -# } -# } diff --git a/apps/emqx/src/emqx_schema.erl b/apps/emqx/src/emqx_schema.erl index ba333f111..c56fc9b48 100644 --- a/apps/emqx/src/emqx_schema.erl +++ b/apps/emqx/src/emqx_schema.erl @@ -779,6 +779,7 @@ fields("listeners") -> map(name, ref("mqtt_tcp_listener")), #{ desc => ?DESC(fields_listeners_tcp), + default => default_listener(tcp), required => {false, recursively} } )}, @@ -787,6 +788,7 @@ fields("listeners") -> map(name, ref("mqtt_ssl_listener")), #{ desc => ?DESC(fields_listeners_ssl), + default => default_listener(ssl), required => {false, recursively} } )}, @@ -795,6 +797,7 @@ fields("listeners") -> map(name, ref("mqtt_ws_listener")), #{ desc => ?DESC(fields_listeners_ws), + default => default_listener(ws), required => {false, recursively} } )}, @@ -803,6 +806,7 @@ fields("listeners") -> map(name, ref("mqtt_wss_listener")), #{ desc => ?DESC(fields_listeners_wss), + default => default_listener(wss), required => {false, recursively} } )}, @@ -3083,3 +3087,52 @@ assert_required_field(Conf, Key, ErrorMessage) -> _ -> ok end. + +default_listener(tcp) -> + #{ + <<"default">> => + #{ + <<"bind">> => <<"0.0.0.0:1883">>, + <<"max_connections">> => 1024000 + } + }; +default_listener(ws) -> + #{ + <<"default">> => + #{ + <<"bind">> => <<"0.0.0.0:8083">>, + <<"max_connections">> => 1024000, + <<"websocket">> => #{<<"mqtt_path">> => <<"/mqtt">>} + } + }; +default_listener(SSLListener) -> + %% The env variable is resolved in emqx_tls_lib + CertFile = fun(Name) -> + iolist_to_binary("${EMQX_ETC_DIR}/" ++ filename:join(["certs", Name])) + end, + SslOptions = #{ + <<"cacertfile">> => CertFile(<<"cacert.pem">>), + <<"certfile">> => CertFile(<<"cert.pem">>), + <<"keyfile">> => CertFile(<<"key.pem">>) + }, + case SSLListener of + ssl -> + #{ + <<"default">> => + #{ + <<"bind">> => <<"0.0.0.0:8883">>, + <<"max_connections">> => 512000, + <<"ssl_options">> => SslOptions + } + }; + wss -> + #{ + <<"default">> => + #{ + <<"bind">> => <<"0.0.0.0:8084">>, + <<"max_connections">> => 512000, + <<"ssl_options">> => SslOptions, + <<"websocket">> => #{<<"mqtt_path">> => <<"/mqtt">>} + } + } + end. diff --git a/apps/emqx/src/emqx_tls_lib.erl b/apps/emqx/src/emqx_tls_lib.erl index d1c57bf0d..c555059cf 100644 --- a/apps/emqx/src/emqx_tls_lib.erl +++ b/apps/emqx/src/emqx_tls_lib.erl @@ -309,19 +309,19 @@ ensure_ssl_files(Dir, SSL, Opts) -> case ensure_ssl_file_key(SSL, RequiredKeys) of ok -> KeyPaths = ?SSL_FILE_OPT_PATHS ++ ?SSL_FILE_OPT_PATHS_A, - ensure_ssl_files(Dir, SSL, KeyPaths, Opts); + ensure_ssl_files_per_key(Dir, SSL, KeyPaths, Opts); {error, _} = Error -> Error end. -ensure_ssl_files(_Dir, SSL, [], _Opts) -> +ensure_ssl_files_per_key(_Dir, SSL, [], _Opts) -> {ok, SSL}; -ensure_ssl_files(Dir, SSL, [KeyPath | KeyPaths], Opts) -> +ensure_ssl_files_per_key(Dir, SSL, [KeyPath | KeyPaths], Opts) -> case ensure_ssl_file(Dir, KeyPath, SSL, emqx_utils_maps:deep_get(KeyPath, SSL, undefined), Opts) of {ok, NewSSL} -> - ensure_ssl_files(Dir, NewSSL, KeyPaths, Opts); + ensure_ssl_files_per_key(Dir, NewSSL, KeyPaths, Opts); {error, Reason} -> {error, Reason#{which_options => [KeyPath]}} end. @@ -347,7 +347,8 @@ delete_ssl_files(Dir, NewOpts0, OldOpts0) -> delete_old_file(New, Old) when New =:= Old -> ok; delete_old_file(_New, _Old = undefined) -> ok; -delete_old_file(_New, Old) -> +delete_old_file(_New, Old0) -> + Old = resolve_cert_path(Old0), case is_generated_file(Old) andalso filelib:is_regular(Old) andalso file:delete(Old) of ok -> ok; @@ -355,7 +356,7 @@ delete_old_file(_New, Old) -> false -> ok; {error, Reason} -> - ?SLOG(error, #{msg => "failed_to_delete_ssl_file", file_path => Old, reason => Reason}) + ?SLOG(error, #{msg => "failed_to_delete_ssl_file", file_path => Old0, reason => Reason}) end. ensure_ssl_file(_Dir, _KeyPath, SSL, undefined, _Opts) -> @@ -414,7 +415,8 @@ is_pem(MaybePem) -> %% To make it simple, the file is always overwritten. %% Also a potentially half-written PEM file (e.g. due to power outage) %% can be corrected with an overwrite. -save_pem_file(Dir, KeyPath, Pem, DryRun) -> +save_pem_file(Dir0, KeyPath, Pem, DryRun) -> + Dir = resolve_cert_path(Dir0), Path = pem_file_name(Dir, KeyPath, Pem), case filelib:ensure_dir(Path) of ok when DryRun -> @@ -472,7 +474,8 @@ hex_str(Bin) -> iolist_to_binary([io_lib:format("~2.16.0b", [X]) || <> <= Bin]). %% @doc Returns 'true' when the file is a valid pem, otherwise {error, Reason}. -is_valid_pem_file(Path) -> +is_valid_pem_file(Path0) -> + Path = resolve_cert_path(Path0), case file:read_file(Path) of {ok, Pem} -> is_pem(Pem) orelse {error, not_pem}; {error, Reason} -> {error, Reason} @@ -513,10 +516,15 @@ do_drop_invalid_certs([KeyPath | KeyPaths], SSL) -> to_server_opts(Type, Opts) -> Versions = integral_versions(Type, maps:get(versions, Opts, undefined)), Ciphers = integral_ciphers(Versions, maps:get(ciphers, Opts, undefined)), - maps:to_list(Opts#{ - ciphers => Ciphers, - versions => Versions - }). + filter( + maps:to_list(Opts#{ + keyfile => resolve_cert_path_strict(maps:get(keyfile, Opts, undefined)), + certfile => resolve_cert_path_strict(maps:get(certfile, Opts, undefined)), + cacertfile => resolve_cert_path_strict(maps:get(cacertfile, Opts, undefined)), + ciphers => Ciphers, + versions => Versions + }) + ). %% @doc Convert hocon-checked tls client options (map()) to %% proplist accepted by ssl library. @@ -532,9 +540,9 @@ to_client_opts(Type, Opts) -> Get = fun(Key) -> GetD(Key, undefined) end, case GetD(enable, false) of true -> - KeyFile = ensure_str(Get(keyfile)), - CertFile = ensure_str(Get(certfile)), - CAFile = ensure_str(Get(cacertfile)), + KeyFile = resolve_cert_path_strict(Get(keyfile)), + CertFile = resolve_cert_path_strict(Get(certfile)), + CAFile = resolve_cert_path_strict(Get(cacertfile)), Verify = GetD(verify, verify_none), SNI = ensure_sni(Get(server_name_indication)), Versions = integral_versions(Type, Get(versions)), @@ -556,6 +564,59 @@ to_client_opts(Type, Opts) -> [] end. +resolve_cert_path_strict(Path) -> + case resolve_cert_path(Path) of + undefined -> + undefined; + ResolvedPath -> + case filelib:is_regular(ResolvedPath) of + true -> + ResolvedPath; + false -> + PathToLog = ensure_str(Path), + LogData = + case PathToLog =:= ResolvedPath of + true -> + #{path => PathToLog}; + false -> + #{path => PathToLog, resolved_path => ResolvedPath} + end, + ?SLOG(error, LogData#{msg => "cert_file_not_found"}), + undefined + end + end. + +resolve_cert_path(undefined) -> + undefined; +resolve_cert_path(Path) -> + case ensure_str(Path) of + "$" ++ Maybe -> + naive_env_resolver(Maybe); + Other -> + Other + end. + +%% resolves a file path like "ENV_VARIABLE/sub/path" or "{ENV_VARIABLE}/sub/path" +%% in windows, it could be "ENV_VARIABLE/sub\path" or "{ENV_VARIABLE}/sub\path" +naive_env_resolver(Maybe) -> + case string:split(Maybe, "/") of + [_] -> + Maybe; + [Env, SubPath] -> + case os:getenv(trim_env_name(Env)) of + false -> + SubPath; + "" -> + SubPath; + EnvValue -> + filename:join(EnvValue, SubPath) + end + end. + +%% delete the first and last curly braces +trim_env_name(Env) -> + string:trim(Env, both, "{}"). + filter([]) -> []; filter([{_, undefined} | T]) -> filter(T); filter([{_, ""} | T]) -> filter(T);