Merge pull request #12606 from JimMoen/EMQX-11914/fix-prom-api-crash

fix(prom): api crash when tls cert file non existed
This commit is contained in:
JimMoen 2024-02-28 16:47:16 +08:00 committed by GitHub
commit eaa0bfd120
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 50 additions and 32 deletions

View File

@ -2,7 +2,7 @@
{application, emqx_prometheus, [ {application, emqx_prometheus, [
{description, "Prometheus for EMQX"}, {description, "Prometheus for EMQX"},
% strict semver, bump manually! % strict semver, bump manually!
{vsn, "5.0.19"}, {vsn, "5.0.20"},
{modules, []}, {modules, []},
{registered, [emqx_prometheus_sup]}, {registered, [emqx_prometheus_sup]},
{applications, [kernel, stdlib, prometheus, emqx, emqx_auth, emqx_resource, emqx_management]}, {applications, [kernel, stdlib, prometheus, emqx, emqx_auth, emqx_resource, emqx_management]},

View File

@ -830,8 +830,7 @@ cert_data(undefined) ->
cert_data(AllListeners) -> cert_data(AllListeners) ->
Points = lists:foldl( Points = lists:foldl(
fun(ListenerType, PointsAcc) -> fun(ListenerType, PointsAcc) ->
PointsAcc ++ lists:append(PointsAcc, points_of_listeners(ListenerType, AllListeners))
points_of_listeners(ListenerType, AllListeners)
end, end,
_PointsInitAcc = [], _PointsInitAcc = [],
?LISTENER_TYPES ?LISTENER_TYPES
@ -843,53 +842,71 @@ cert_data(AllListeners) ->
points_of_listeners(Type, AllListeners) -> points_of_listeners(Type, AllListeners) ->
do_points_of_listeners(Type, maps:get(Type, AllListeners, undefined)). do_points_of_listeners(Type, maps:get(Type, AllListeners, undefined)).
-define(CERT_TYPES, [certfile]). -spec do_points_of_listeners(Type, Listeners) ->
-spec do_points_of_listeners(Type, TypeOfListeners) ->
[_Point :: {[{LabelKey, LabelValue}], Epoch}] [_Point :: {[{LabelKey, LabelValue}], Epoch}]
when when
Type :: ssl | wss | quic, Type :: ssl | wss | quic,
TypeOfListeners :: #{ListenerName :: atom() => ListenerConf :: map()} | undefined, Listeners :: #{ListenerName :: atom() => ListenerConf :: map()} | undefined,
LabelKey :: atom(), LabelKey :: atom(),
LabelValue :: atom(), LabelValue :: atom(),
Epoch :: non_neg_integer(). Epoch :: non_neg_integer().
do_points_of_listeners(_, undefined) -> do_points_of_listeners(_, undefined) ->
[]; [];
do_points_of_listeners(ListenerType, TypeOfListeners) -> do_points_of_listeners(Type, Listeners) ->
lists:foldl( lists:foldl(
fun(Name, PointsAcc) -> fun(Name, PointsAcc) ->
lists:foldl( case
fun(CertType, AccIn) -> emqx_utils_maps:deep_get([Name, enable], Listeners, false) andalso
case emqx_utils_maps:deep_get(
emqx_utils_maps:deep_get( [Name, ssl_options, certfile], Listeners, undefined
[Name, ssl_options, CertType], TypeOfListeners, undefined )
) of
of false -> PointsAcc;
undefined -> AccIn; undefined -> PointsAcc;
Path -> [gen_point(ListenerType, Name, Path) | AccIn] Path -> [gen_point_cert_expiry_at(Type, Name, Path) | PointsAcc]
end end
end,
[],
?CERT_TYPES
) ++ PointsAcc
end, end,
[], [],
maps:keys(TypeOfListeners) %% listener names
maps:keys(Listeners)
). ).
gen_point(Type, Name, Path) -> gen_point_cert_expiry_at(Type, Name, Path) ->
{[{listener_type, Type}, {listener_name, Name}], cert_expiry_at_from_path(Path)}. {[{listener_type, Type}, {listener_name, Name}], cert_expiry_at_from_path(Path)}.
%% TODO: cert manager for more generic utils functions %% TODO: cert manager for more generic utils functions
cert_expiry_at_from_path(Path0) -> cert_expiry_at_from_path(Path0) ->
Path = emqx_schema:naive_env_interpolation(Path0), Path = emqx_schema:naive_env_interpolation(Path0),
{ok, PemBin} = file:read_file(Path), try
[CertEntry | _] = public_key:pem_decode(PemBin), case file:read_file(Path) of
Cert = public_key:pem_entry_decode(CertEntry), {ok, PemBin} ->
%% TODO: Not fully tested for all certs type [CertEntry | _] = public_key:pem_decode(PemBin),
{'utcTime', NotAfterUtc} = Cert = public_key:pem_entry_decode(CertEntry),
Cert#'Certificate'.'tbsCertificate'#'TBSCertificate'.validity#'Validity'.'notAfter', %% TODO: Not fully tested for all certs type
utc_time_to_epoch(NotAfterUtc). {'utcTime', NotAfterUtc} =
Cert#'Certificate'.'tbsCertificate'#'TBSCertificate'.validity#'Validity'.'notAfter',
utc_time_to_epoch(NotAfterUtc);
{error, Reason} ->
?SLOG(error, #{
msg => "read_cert_file_failed",
path => Path0,
resolved_path => Path,
reason => Reason
}),
0
end
catch
E:R:S ->
?SLOG(error, #{
msg => "obtain_cert_expiry_time_failed",
error => E,
reason => R,
stacktrace => S,
path => Path0,
resolved_path => Path
}),
0
end.
utc_time_to_epoch(UtcTime) -> utc_time_to_epoch(UtcTime) ->
date_to_expiry_epoch(utc_time_to_datetime(UtcTime)). date_to_expiry_epoch(utc_time_to_datetime(UtcTime)).
@ -898,7 +915,7 @@ utc_time_to_datetime(Str) ->
{ok, [Year, Month, Day, Hour, Minute, Second], _} = io_lib:fread( {ok, [Year, Month, Day, Hour, Minute, Second], _} = io_lib:fread(
"~2d~2d~2d~2d~2d~2dZ", Str "~2d~2d~2d~2d~2d~2dZ", Str
), ),
%% Alwoys Assuming YY is in 2000 %% Always Assuming YY is in 2000
{{2000 + Year, Month, Day}, {Hour, Minute, Second}}. {{2000 + Year, Month, Day}, {Hour, Minute, Second}}.
%% 62167219200 =:= calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}). %% 62167219200 =:= calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}).

View File

@ -0,0 +1 @@
Fix the issue of the endpoint `/prometheus/stats` crashing when the listener's cert file is unreadable.