Merge pull request #11028 from id/0612-fix-incompatible-tls-options-and-version-gap
fix(tls): fix incompatible tls options and issue with tls version gap
This commit is contained in:
commit
78c52c28c4
|
@ -25,7 +25,7 @@
|
||||||
{emqx_utils, {path, "../emqx_utils"}},
|
{emqx_utils, {path, "../emqx_utils"}},
|
||||||
{lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}},
|
{lc, {git, "https://github.com/emqx/lc.git", {tag, "0.3.2"}}},
|
||||||
{gproc, {git, "https://github.com/emqx/gproc", {tag, "0.9.0.1"}}},
|
{gproc, {git, "https://github.com/emqx/gproc", {tag, "0.9.0.1"}}},
|
||||||
{cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}},
|
{cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.2"}}},
|
||||||
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.6"}}},
|
{esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.6"}}},
|
||||||
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.15.2"}}},
|
{ekka, {git, "https://github.com/emqx/ekka", {tag, "0.15.2"}}},
|
||||||
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}},
|
{gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.8.1"}}},
|
||||||
|
|
|
@ -2679,10 +2679,30 @@ validate_ciphers(Ciphers) ->
|
||||||
validate_tls_versions(Collection, Versions) ->
|
validate_tls_versions(Collection, Versions) ->
|
||||||
AvailableVersions = available_tls_vsns(Collection),
|
AvailableVersions = available_tls_vsns(Collection),
|
||||||
case lists:filter(fun(V) -> not lists:member(V, AvailableVersions) end, Versions) of
|
case lists:filter(fun(V) -> not lists:member(V, AvailableVersions) end, Versions) of
|
||||||
[] -> ok;
|
[] -> validate_tls_version_gap(Versions);
|
||||||
Vs -> {error, {unsupported_tls_versions, Vs}}
|
Vs -> {error, {unsupported_tls_versions, Vs}}
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% See also `validate_version_gap/1` in OTP ssl.erl,
|
||||||
|
%% e.g: https://github.com/emqx/otp/blob/emqx-OTP-25.1.2/lib/ssl/src/ssl.erl#L2566.
|
||||||
|
%% Do not allow configuration of TLS 1.3 with a gap where TLS 1.2 is not supported
|
||||||
|
%% as that configuration can trigger the built in version downgrade protection
|
||||||
|
%% mechanism and the handshake can fail with an Illegal Parameter alert.
|
||||||
|
validate_tls_version_gap(Versions) ->
|
||||||
|
case lists:member('tlsv1.3', Versions) of
|
||||||
|
true when length(Versions) >= 2 ->
|
||||||
|
case lists:member('tlsv1.2', Versions) of
|
||||||
|
true ->
|
||||||
|
ok;
|
||||||
|
false ->
|
||||||
|
{error,
|
||||||
|
"Using multiple versions that include tlsv1.3 but "
|
||||||
|
"exclude tlsv1.2 is not allowed"}
|
||||||
|
end;
|
||||||
|
_ ->
|
||||||
|
ok
|
||||||
|
end.
|
||||||
|
|
||||||
validations() ->
|
validations() ->
|
||||||
[
|
[
|
||||||
{check_process_watermark, fun check_process_watermark/1},
|
{check_process_watermark, fun check_process_watermark/1},
|
||||||
|
|
|
@ -478,7 +478,7 @@ to_server_opts(Type, Opts) ->
|
||||||
Versions = integral_versions(Type, maps:get(versions, Opts, undefined)),
|
Versions = integral_versions(Type, maps:get(versions, Opts, undefined)),
|
||||||
Ciphers = integral_ciphers(Versions, maps:get(ciphers, Opts, undefined)),
|
Ciphers = integral_ciphers(Versions, maps:get(ciphers, Opts, undefined)),
|
||||||
Path = fun(Key) -> resolve_cert_path_for_read_strict(maps:get(Key, Opts, undefined)) end,
|
Path = fun(Key) -> resolve_cert_path_for_read_strict(maps:get(Key, Opts, undefined)) end,
|
||||||
filter(
|
ensure_valid_options(
|
||||||
maps:to_list(Opts#{
|
maps:to_list(Opts#{
|
||||||
keyfile => Path(keyfile),
|
keyfile => Path(keyfile),
|
||||||
certfile => Path(certfile),
|
certfile => Path(certfile),
|
||||||
|
@ -511,7 +511,7 @@ to_client_opts(Type, Opts) ->
|
||||||
SNI = ensure_sni(Get(server_name_indication)),
|
SNI = ensure_sni(Get(server_name_indication)),
|
||||||
Versions = integral_versions(Type, Get(versions)),
|
Versions = integral_versions(Type, Get(versions)),
|
||||||
Ciphers = integral_ciphers(Versions, Get(ciphers)),
|
Ciphers = integral_ciphers(Versions, Get(ciphers)),
|
||||||
filter(
|
ensure_valid_options(
|
||||||
[
|
[
|
||||||
{keyfile, KeyFile},
|
{keyfile, KeyFile},
|
||||||
{certfile, CertFile},
|
{certfile, CertFile},
|
||||||
|
@ -556,33 +556,72 @@ resolve_cert_path_for_read_strict(Path) ->
|
||||||
resolve_cert_path_for_read(Path) ->
|
resolve_cert_path_for_read(Path) ->
|
||||||
emqx_schema:naive_env_interpolation(Path).
|
emqx_schema:naive_env_interpolation(Path).
|
||||||
|
|
||||||
filter([], _) ->
|
ensure_valid_options(Options, Versions) ->
|
||||||
[];
|
ensure_valid_options(Options, Versions, []).
|
||||||
filter([{_, undefined} | T], Versions) ->
|
|
||||||
filter(T, Versions);
|
ensure_valid_options([], _, Acc) ->
|
||||||
filter([{_, ""} | T], Versions) ->
|
lists:reverse(Acc);
|
||||||
filter(T, Versions);
|
ensure_valid_options([{_, undefined} | T], Versions, Acc) ->
|
||||||
filter([{K, V} | T], Versions) ->
|
ensure_valid_options(T, Versions, Acc);
|
||||||
|
ensure_valid_options([{_, ""} | T], Versions, Acc) ->
|
||||||
|
ensure_valid_options(T, Versions, Acc);
|
||||||
|
ensure_valid_options([{K, V} | T], Versions, Acc) ->
|
||||||
case tls_option_compatible_versions(K) of
|
case tls_option_compatible_versions(K) of
|
||||||
all ->
|
all ->
|
||||||
[{K, V} | filter(T, Versions)];
|
ensure_valid_options(T, Versions, [{K, V} | Acc]);
|
||||||
CompatibleVersions ->
|
CompatibleVersions ->
|
||||||
case CompatibleVersions -- (CompatibleVersions -- Versions) of
|
Enabled = sets:from_list(Versions),
|
||||||
[] ->
|
Compatible = sets:from_list(CompatibleVersions),
|
||||||
filter(T, Versions);
|
case sets:size(sets:intersection(Enabled, Compatible)) > 0 of
|
||||||
_ ->
|
true ->
|
||||||
[{K, V} | filter(T, Versions)]
|
ensure_valid_options(T, Versions, [{K, V} | Acc]);
|
||||||
|
false ->
|
||||||
|
?SLOG(warning, #{
|
||||||
|
msg => "drop_incompatible_tls_option", option => K, versions => Versions
|
||||||
|
}),
|
||||||
|
ensure_valid_options(T, Versions, Acc)
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% see otp/lib/ssl/src/ssl.erl, `assert_option_dependency/4`
|
||||||
|
tls_option_compatible_versions(beast_mitigation) ->
|
||||||
|
[dtlsv1, 'tlsv1'];
|
||||||
|
tls_option_compatible_versions(padding_check) ->
|
||||||
|
[dtlsv1, 'tlsv1'];
|
||||||
|
tls_option_compatible_versions(client_renegotiation) ->
|
||||||
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
|
tls_option_compatible_versions(reuse_session) ->
|
||||||
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
tls_option_compatible_versions(reuse_sessions) ->
|
tls_option_compatible_versions(reuse_sessions) ->
|
||||||
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
tls_option_compatible_versions(secure_renegotiate) ->
|
tls_option_compatible_versions(secure_renegotiate) ->
|
||||||
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
|
tls_option_compatible_versions(next_protocol_advertised) ->
|
||||||
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
|
tls_option_compatible_versions(client_preferred_next_protocols) ->
|
||||||
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
|
tls_option_compatible_versions(psk_identity) ->
|
||||||
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
|
tls_option_compatible_versions(srp_identity) ->
|
||||||
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
tls_option_compatible_versions(user_lookup_fun) ->
|
tls_option_compatible_versions(user_lookup_fun) ->
|
||||||
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
||||||
tls_option_compatible_versions(client_renegotiation) ->
|
tls_option_compatible_versions(early_data) ->
|
||||||
[dtlsv1, 'dtlsv1.2', 'tlsv1', 'tlsv1.1', 'tlsv1.2'];
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(certificate_authorities) ->
|
||||||
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(cookie) ->
|
||||||
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(key_update_at) ->
|
||||||
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(anti_replay) ->
|
||||||
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(session_tickets) ->
|
||||||
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(supported_groups) ->
|
||||||
|
['tlsv1.3'];
|
||||||
|
tls_option_compatible_versions(use_ticket) ->
|
||||||
|
['tlsv1.3'];
|
||||||
tls_option_compatible_versions(_) ->
|
tls_option_compatible_versions(_) ->
|
||||||
all.
|
all.
|
||||||
|
|
||||||
|
|
|
@ -94,6 +94,18 @@ ssl_opts_tls_psk_test() ->
|
||||||
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>]}),
|
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>]}),
|
||||||
?assertMatch(#{versions := ['tlsv1.2']}, Checked).
|
?assertMatch(#{versions := ['tlsv1.2']}, Checked).
|
||||||
|
|
||||||
|
ssl_opts_version_gap_test_() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
|
RanchSc = emqx_schema:server_ssl_opts_schema(#{}, true),
|
||||||
|
Reason = "Using multiple versions that include tlsv1.3 but exclude tlsv1.2 is not allowed",
|
||||||
|
[
|
||||||
|
?_assertThrow(
|
||||||
|
{_, [#{kind := validation_error, reason := Reason}]},
|
||||||
|
validate(S, #{<<"versions">> => [<<"tlsv1.1">>, <<"tlsv1.3">>]})
|
||||||
|
)
|
||||||
|
|| S <- [Sc, RanchSc]
|
||||||
|
].
|
||||||
|
|
||||||
bad_cipher_test() ->
|
bad_cipher_test() ->
|
||||||
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
Reason = {bad_ciphers, ["foo"]},
|
Reason = {bad_ciphers, ["foo"]},
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
Disallow using multiple TLS versions in the listener config that include tlsv1.3 but exclude tlsv1.2.
|
||||||
|
|
||||||
|
Using TLS configuration with such version gap caused connection errors.
|
||||||
|
Additionally, drop and log TLS options that are incompatible with the selected TLS version(s).
|
||||||
|
|
||||||
|
Note: any old listener configuration with the version gap described above will fail to load
|
||||||
|
after applying this fix and must be manually fixed.
|
4
mix.exs
4
mix.exs
|
@ -52,7 +52,7 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
{:ehttpc, github: "emqx/ehttpc", tag: "0.4.10", override: true},
|
{:ehttpc, github: "emqx/ehttpc", tag: "0.4.10", override: true},
|
||||||
{:gproc, github: "emqx/gproc", tag: "0.9.0.1", override: true},
|
{:gproc, github: "emqx/gproc", tag: "0.9.0.1", override: true},
|
||||||
{:jiffy, github: "emqx/jiffy", tag: "1.0.5", override: true},
|
{:jiffy, github: "emqx/jiffy", tag: "1.0.5", override: true},
|
||||||
{:cowboy, github: "emqx/cowboy", tag: "2.9.0", override: true},
|
{:cowboy, github: "emqx/cowboy", tag: "2.9.2", override: true},
|
||||||
{:esockd, github: "emqx/esockd", tag: "5.9.6", override: true},
|
{:esockd, github: "emqx/esockd", tag: "5.9.6", override: true},
|
||||||
{:rocksdb, github: "emqx/erlang-rocksdb", tag: "1.7.2-emqx-11", override: true},
|
{:rocksdb, github: "emqx/erlang-rocksdb", tag: "1.7.2-emqx-11", override: true},
|
||||||
{:ekka, github: "emqx/ekka", tag: "0.15.2", override: true},
|
{:ekka, github: "emqx/ekka", tag: "0.15.2", override: true},
|
||||||
|
@ -92,7 +92,7 @@ defmodule EMQXUmbrella.MixProject do
|
||||||
github: "ninenines/cowlib", ref: "c6553f8308a2ca5dcd69d845f0a7d098c40c3363", override: true},
|
github: "ninenines/cowlib", ref: "c6553f8308a2ca5dcd69d845f0a7d098c40c3363", override: true},
|
||||||
# in conflict by cowboy_swagger and cowboy
|
# in conflict by cowboy_swagger and cowboy
|
||||||
{:ranch,
|
{:ranch,
|
||||||
github: "ninenines/ranch", ref: "a692f44567034dacf5efcaa24a24183788594eb7", override: true},
|
github: "emqx/ranch", ref: "de8ba2a00817c0a6eb1b8f20d6fb3e44e2c9a5aa", override: true},
|
||||||
# in conflict by grpc and eetcd
|
# in conflict by grpc and eetcd
|
||||||
{:gpb, "4.19.7", override: true, runtime: false},
|
{:gpb, "4.19.7", override: true, runtime: false},
|
||||||
{:hackney, github: "emqx/hackney", tag: "1.18.1-1", override: true}
|
{:hackney, github: "emqx/hackney", tag: "1.18.1-1", override: true}
|
||||||
|
|
|
@ -59,7 +59,7 @@
|
||||||
, {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.4.10"}}}
|
, {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.4.10"}}}
|
||||||
, {gproc, {git, "https://github.com/emqx/gproc", {tag, "0.9.0.1"}}}
|
, {gproc, {git, "https://github.com/emqx/gproc", {tag, "0.9.0.1"}}}
|
||||||
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
||||||
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.0"}}}
|
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.9.2"}}}
|
||||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.6"}}}
|
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.9.6"}}}
|
||||||
, {rocksdb, {git, "https://github.com/emqx/erlang-rocksdb", {tag, "1.7.2-emqx-11"}}}
|
, {rocksdb, {git, "https://github.com/emqx/erlang-rocksdb", {tag, "1.7.2-emqx-11"}}}
|
||||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.15.2"}}}
|
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.15.2"}}}
|
||||||
|
|
Loading…
Reference in New Issue