Merge pull request #5777 from zmstone/fix-ssl-listener-config
fix(emqx_schema): make ssl config schema right
This commit is contained in:
commit
357456880e
|
@ -194,12 +194,17 @@ listeners.ssl.default {
|
||||||
mountpoint = ""
|
mountpoint = ""
|
||||||
|
|
||||||
## SSL options
|
## SSL options
|
||||||
## See ${example_common_ssl_options} for more information
|
|
||||||
ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
|
|
||||||
ssl.keyfile = "{{ platform_etc_dir }}/certs/key.pem"
|
ssl.keyfile = "{{ platform_etc_dir }}/certs/key.pem"
|
||||||
ssl.certfile = "{{ platform_etc_dir }}/certs/cert.pem"
|
ssl.certfile = "{{ platform_etc_dir }}/certs/cert.pem"
|
||||||
ssl.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem"
|
ssl.cacertfile = "{{ platform_etc_dir }}/certs/cacert.pem"
|
||||||
|
|
||||||
|
ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
|
||||||
|
# TLS 1.3: "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256"
|
||||||
|
# TLS 1-1.2 "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA"
|
||||||
|
# PSK: "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA"
|
||||||
|
# NOTE: If PSK cipher-suites are intended, tlsv1.3 should not be enabled in 'versions' config
|
||||||
|
# ssl.ciphers = ""
|
||||||
|
|
||||||
## TCP options
|
## TCP options
|
||||||
## See ${example_common_tcp_options} for more information
|
## See ${example_common_tcp_options} for more information
|
||||||
tcp.backlog = 1024
|
tcp.backlog = 1024
|
||||||
|
@ -1345,12 +1350,13 @@ example_common_ssl_options {
|
||||||
## Default: true
|
## Default: true
|
||||||
ssl.honor_cipher_order = true
|
ssl.honor_cipher_order = true
|
||||||
|
|
||||||
## TLS versions only to protect from POODLE attack.
|
|
||||||
##
|
|
||||||
## @doc listeners.<name>.ssl.versions
|
|
||||||
## ValueType: Array<TLSVersion>
|
|
||||||
## Default: ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
|
|
||||||
ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
|
ssl.versions = ["tlsv1.3", "tlsv1.2", "tlsv1.1", "tlsv1"]
|
||||||
|
# TLS 1.3: "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,TLS_CHACHA20_POLY1305_SHA256,TLS_AES_128_CCM_SHA256,TLS_AES_128_CCM_8_SHA256"
|
||||||
|
# TLS 1-1.2 "ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA"
|
||||||
|
# PSK: "PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA"
|
||||||
|
# NOTE: If PSK cipher-suites are intended, tlsv1.3 should not be enabled in 'versions' config
|
||||||
|
# NOTE: by default, ALL ciphers are enabled
|
||||||
|
# ssl.ciphers = ""
|
||||||
|
|
||||||
## TLS Handshake timeout.
|
## TLS Handshake timeout.
|
||||||
##
|
##
|
||||||
|
@ -1446,27 +1452,6 @@ example_common_ssl_options {
|
||||||
## Default: true
|
## Default: true
|
||||||
ssl.fail_if_no_peer_cert = false
|
ssl.fail_if_no_peer_cert = false
|
||||||
|
|
||||||
## This is the single most important configuration option of an Erlang SSL
|
|
||||||
## application. Ciphers (and their ordering) define the way the client and
|
|
||||||
## server encrypt information over the wire, from the initial Diffie-Helman
|
|
||||||
## key exchange, the session key encryption ## algorithm and the message
|
|
||||||
## digest algorithm. Selecting a good cipher suite is critical for the
|
|
||||||
## application’s data security, confidentiality and performance.
|
|
||||||
##
|
|
||||||
## The cipher list above offers:
|
|
||||||
##
|
|
||||||
## A good balance between compatibility with older browsers.
|
|
||||||
## It can get stricter for Machine-To-Machine scenarios.
|
|
||||||
## Perfect Forward Secrecy.
|
|
||||||
## No old/insecure encryption and HMAC algorithms
|
|
||||||
##
|
|
||||||
## Most of it was copied from Mozilla’s Server Side TLS article
|
|
||||||
##
|
|
||||||
## @doc listeners.<name>.ssl.ciphers
|
|
||||||
## ValueType: Array<Cipher>
|
|
||||||
## Default: [ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA,PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA]
|
|
||||||
ssl.ciphers = [ECDHE-ECDSA-AES256-GCM-SHA384,ECDHE-RSA-AES256-GCM-SHA384,ECDHE-ECDSA-AES256-SHA384,ECDHE-RSA-AES256-SHA384,ECDHE-ECDSA-DES-CBC3-SHA,ECDH-ECDSA-AES256-GCM-SHA384,ECDH-RSA-AES256-GCM-SHA384,ECDH-ECDSA-AES256-SHA384,ECDH-RSA-AES256-SHA384,DHE-DSS-AES256-GCM-SHA384,DHE-DSS-AES256-SHA256,AES256-GCM-SHA384,AES256-SHA256,ECDHE-ECDSA-AES128-GCM-SHA256,ECDHE-RSA-AES128-GCM-SHA256,ECDHE-ECDSA-AES128-SHA256,ECDHE-RSA-AES128-SHA256,ECDH-ECDSA-AES128-GCM-SHA256,ECDH-RSA-AES128-GCM-SHA256,ECDH-ECDSA-AES128-SHA256,ECDH-RSA-AES128-SHA256,DHE-DSS-AES128-GCM-SHA256,DHE-DSS-AES128-SHA256,AES128-GCM-SHA256,AES128-SHA256,ECDHE-ECDSA-AES256-SHA,ECDHE-RSA-AES256-SHA,DHE-DSS-AES256-SHA,ECDH-ECDSA-AES256-SHA,ECDH-RSA-AES256-SHA,AES256-SHA,ECDHE-ECDSA-AES128-SHA,ECDHE-RSA-AES128-SHA,DHE-DSS-AES128-SHA,ECDH-ECDSA-AES128-SHA,ECDH-RSA-AES128-SHA,AES128-SHA,PSK-AES128-CBC-SHA,PSK-AES256-CBC-SHA,PSK-3DES-EDE-CBC-SHA,PSK-RC4-SHA]
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
## Socket options for websocket connections
|
## Socket options for websocket connections
|
||||||
|
|
|
@ -10,13 +10,13 @@
|
||||||
%% `git_subdir` dependency in other projects.
|
%% `git_subdir` dependency in other projects.
|
||||||
{deps,
|
{deps,
|
||||||
[ {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
|
[ {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
|
||||||
, {typerefl, {git, "https://github.com/k32/typerefl", {tag, "0.8.4"}}}
|
, {typerefl, {git, "https://github.com/k32/typerefl", {tag, "0.8.5"}}}
|
||||||
, {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.8.3"}}}
|
, {cowboy, {git, "https://github.com/emqx/cowboy", {tag, "2.8.3"}}}
|
||||||
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
, {esockd, {git, "https://github.com/emqx/esockd", {tag, "5.8.2"}}}
|
||||||
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.8"}}}
|
, {ekka, {git, "https://github.com/emqx/ekka", {tag, "0.10.8"}}}
|
||||||
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
, {gen_rpc, {git, "https://github.com/emqx/gen_rpc", {tag, "2.5.1"}}}
|
||||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.19.0"}}}
|
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.19.5"}}}
|
||||||
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
, {pbkdf2, {git, "https://github.com/emqx/erlang-pbkdf2.git", {tag, "2.0.4"}}}
|
||||||
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
, {recon, {git, "https://github.com/ferd/recon", {tag, "2.5.1"}}}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
||||||
|
|
|
@ -294,7 +294,8 @@ fill_defaults(RawConf) ->
|
||||||
-spec fill_defaults(module(), raw_config()) -> map().
|
-spec fill_defaults(module(), raw_config()) -> map().
|
||||||
fill_defaults(SchemaMod, RawConf) ->
|
fill_defaults(SchemaMod, RawConf) ->
|
||||||
hocon_schema:check_plain(SchemaMod, RawConf,
|
hocon_schema:check_plain(SchemaMod, RawConf,
|
||||||
#{nullable => true, no_conversion => true}, root_names_from_conf(RawConf)).
|
#{nullable => true, only_fill_defaults => true},
|
||||||
|
root_names_from_conf(RawConf)).
|
||||||
|
|
||||||
-spec read_override_conf() -> raw_config().
|
-spec read_override_conf() -> raw_config().
|
||||||
read_override_conf() ->
|
read_override_conf() ->
|
||||||
|
|
|
@ -71,7 +71,7 @@
|
||||||
|
|
||||||
-export([namespace/0, roots/0, roots/1, fields/1]).
|
-export([namespace/0, roots/0, roots/1, fields/1]).
|
||||||
-export([conf_get/2, conf_get/3, keys/2, filter/1]).
|
-export([conf_get/2, conf_get/3, keys/2, filter/1]).
|
||||||
-export([ssl/1]).
|
-export([server_ssl_opts_schema/2, client_ssl_opts_schema/1, ciphers_schema/1, default_ciphers/1]).
|
||||||
|
|
||||||
namespace() -> undefined.
|
namespace() -> undefined.
|
||||||
|
|
||||||
|
@ -87,23 +87,26 @@ roots(high) ->
|
||||||
}
|
}
|
||||||
, {"zones",
|
, {"zones",
|
||||||
sc(map("name", ref("zone")),
|
sc(map("name", ref("zone")),
|
||||||
#{ desc => "A zone is a set of configs grouped by the zone <code>name</code>. <br>"
|
#{ desc =>
|
||||||
"For flexible configuration mapping, the <code>name</code> "
|
"""A zone is a set of configs grouped by the zone <code>name</code>.<br>
|
||||||
"can be set to a listener's <code>zone</code> config.<br>"
|
For flexible configuration mapping, the <code>name</code>
|
||||||
"NOTE: A builtin zone named <code>default</code> is auto created "
|
can be set to a listener's <code>zone</code> config.<br>
|
||||||
"and can not be deleted."
|
NOTE: A builtin zone named <code>default</code> is auto created
|
||||||
|
and can not be deleted."""
|
||||||
})}
|
})}
|
||||||
, {"mqtt",
|
, {"mqtt",
|
||||||
sc(ref("mqtt"),
|
sc(ref("mqtt"),
|
||||||
#{ desc => "Global MQTT configuration.<br>"
|
#{ desc =>
|
||||||
"The configs here work as default values which can be overriden "
|
"""Global MQTT configuration.<br>
|
||||||
"in <code>zone</code> configs"
|
The configs here work as default values which can be overriden
|
||||||
|
in <code>zone</code> configs"""
|
||||||
})}
|
})}
|
||||||
, {"authentication",
|
, {"authentication",
|
||||||
sc(hoconsc:lazy(hoconsc:array(map())),
|
sc(hoconsc:lazy(hoconsc:array(map())),
|
||||||
#{ desc => "Default authentication configs for all MQTT listeners.<br>"
|
#{ desc =>
|
||||||
"For per-listener overrides see <code>authentication</code> "
|
"""Default authentication configs for all MQTT listeners.<br>
|
||||||
"in listener configs"
|
For per-listener overrides see <code>authentication</code>
|
||||||
|
in listener configs"""
|
||||||
})}
|
})}
|
||||||
, {"authorization",
|
, {"authorization",
|
||||||
sc(ref("authorization"),
|
sc(ref("authorization"),
|
||||||
|
@ -483,7 +486,7 @@ fields("mqtt_wss_listener") ->
|
||||||
#{})
|
#{})
|
||||||
}
|
}
|
||||||
, {"ssl",
|
, {"ssl",
|
||||||
sc(ref("listener_ssl_opts"),
|
sc(ref("listener_wss_opts"),
|
||||||
#{})
|
#{})
|
||||||
}
|
}
|
||||||
, {"websocket",
|
, {"websocket",
|
||||||
|
@ -498,6 +501,7 @@ fields("mqtt_quic_listener") ->
|
||||||
#{ default => true
|
#{ default => true
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
%% TODO: ensure cacertfile is configurable
|
||||||
, {"certfile",
|
, {"certfile",
|
||||||
sc(string(),
|
sc(string(),
|
||||||
#{})
|
#{})
|
||||||
|
@ -506,11 +510,7 @@ fields("mqtt_quic_listener") ->
|
||||||
sc(string(),
|
sc(string(),
|
||||||
#{})
|
#{})
|
||||||
}
|
}
|
||||||
, {"ciphers",
|
, {"ciphers", ciphers_schema(quic)}
|
||||||
sc(comma_separated_list(),
|
|
||||||
#{ default => "TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256,"
|
|
||||||
"TLS_CHACHA20_POLY1305_SHA256"
|
|
||||||
})}
|
|
||||||
, {"idle_timeout",
|
, {"idle_timeout",
|
||||||
sc(duration(),
|
sc(duration(),
|
||||||
#{ default => "15s"
|
#{ default => "15s"
|
||||||
|
@ -634,12 +634,22 @@ fields("tcp_opts") ->
|
||||||
];
|
];
|
||||||
|
|
||||||
fields("listener_ssl_opts") ->
|
fields("listener_ssl_opts") ->
|
||||||
ssl(#{handshake_timeout => "15s"
|
server_ssl_opts_schema(
|
||||||
, depth => 10
|
#{ depth => 10
|
||||||
, reuse_sessions => true
|
, reuse_sessions => true
|
||||||
, versions => default_tls_vsns()
|
, versions => tcp
|
||||||
, ciphers => default_ciphers()
|
, ciphers => tcp_all
|
||||||
});
|
}, false);
|
||||||
|
|
||||||
|
fields("listener_wss_opts") ->
|
||||||
|
server_ssl_opts_schema(
|
||||||
|
#{ depth => 10
|
||||||
|
, reuse_sessions => true
|
||||||
|
, versions => tcp
|
||||||
|
, ciphers => tcp_all
|
||||||
|
}, true);
|
||||||
|
fields(ssl_client_opts) ->
|
||||||
|
client_ssl_opts_schema(#{});
|
||||||
|
|
||||||
fields("deflate_opts") ->
|
fields("deflate_opts") ->
|
||||||
[ {"level",
|
[ {"level",
|
||||||
|
@ -902,7 +912,10 @@ conf_get(Key, Conf, Default) ->
|
||||||
filter(Opts) ->
|
filter(Opts) ->
|
||||||
[{K, V} || {K, V} <- Opts, V =/= undefined].
|
[{K, V} || {K, V} <- Opts, V =/= undefined].
|
||||||
|
|
||||||
ssl(Defaults) ->
|
%% @private This function defines the SSL opts which are commonly used by
|
||||||
|
%% SSL listener and client.
|
||||||
|
-spec common_ssl_opts_schema(map()) -> hocon_schema:field_schema().
|
||||||
|
common_ssl_opts_schema(Defaults) ->
|
||||||
D = fun (Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
|
D = fun (Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
|
||||||
Df = fun (Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
|
Df = fun (Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
|
||||||
[ {"enable",
|
[ {"enable",
|
||||||
|
@ -913,16 +926,39 @@ ssl(Defaults) ->
|
||||||
, {"cacertfile",
|
, {"cacertfile",
|
||||||
sc(string(),
|
sc(string(),
|
||||||
#{ default => D("cacertfile")
|
#{ default => D("cacertfile")
|
||||||
|
, nullable => true
|
||||||
|
, desc =>
|
||||||
|
"""Trusted PEM format CA certificates bundle file.<br>
|
||||||
|
The certificates in this file are used to verify the TLS peer's certificates.
|
||||||
|
Append new certificates to the file if new CAs are to be trusted.
|
||||||
|
There is no need to restart EMQ X to have the updated file loaded, because
|
||||||
|
the system regularly checks if file has been updated (and reload).<br>
|
||||||
|
NOTE: invalidating (deleting) a certificate from the file will not affect
|
||||||
|
already established connections.
|
||||||
|
"""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
, {"certfile",
|
, {"certfile",
|
||||||
sc(string(),
|
sc(string(),
|
||||||
#{ default => D("certfile")
|
#{ default => D("certfile")
|
||||||
|
, nullable => true
|
||||||
|
, desc =>
|
||||||
|
"""PEM format certificates chain file.<br>
|
||||||
|
The certificates in this file should be in reversed order of the certificate
|
||||||
|
issue chain. That is, the host's certificate should be placed in the beginning
|
||||||
|
of the file, followed by the immediate issuer certificate and so on.
|
||||||
|
Although the root CA certificate is optional, it should placed at the end of
|
||||||
|
the file if it is to be added.
|
||||||
|
"""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
, {"keyfile",
|
, {"keyfile",
|
||||||
sc(string(),
|
sc(string(),
|
||||||
#{ default => D("keyfile")
|
#{ default => D("keyfile")
|
||||||
|
, nullable => true
|
||||||
|
, desc =>
|
||||||
|
"""PEM format private key file.<br>
|
||||||
|
"""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
, {"verify",
|
, {"verify",
|
||||||
|
@ -930,9 +966,41 @@ ssl(Defaults) ->
|
||||||
#{ default => Df("verify", verify_none)
|
#{ default => Df("verify", verify_none)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
, {"fail_if_no_peer_cert",
|
, {"reuse_sessions",
|
||||||
sc(boolean(),
|
sc(boolean(),
|
||||||
#{ default => Df("fail_if_no_peer_cert", false)
|
#{ default => Df("reuse_sessions", true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
, {"depth",
|
||||||
|
sc(integer(),
|
||||||
|
#{default => Df("depth", 10)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
, {"password",
|
||||||
|
sc(string(),
|
||||||
|
#{ sensitive => true
|
||||||
|
, nullable => true
|
||||||
|
, desc =>
|
||||||
|
"""String containing the user's password. Only used if the private
|
||||||
|
keyfile is password-protected."""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
, {"versions",
|
||||||
|
sc(hoconsc:array(typerefl:atom()),
|
||||||
|
#{ default => default_tls_vsns(maps:get(versions, Defaults, tcp))
|
||||||
|
, desc =>
|
||||||
|
"""All TLS/DTLS versions to be supported.<br>
|
||||||
|
NOTE: PSK ciphers are suppresed by 'tlsv1.3' version config<br>
|
||||||
|
In case PSK cipher suites are intended, make sure to configured
|
||||||
|
<code>['tlsv1.2', 'tlsv1.1']</code> here.
|
||||||
|
"""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
, {"ciphers", ciphers_schema(D("ciphers"))}
|
||||||
|
, {user_lookup_fun,
|
||||||
|
sc(typerefl:alias("string", any()),
|
||||||
|
#{ default => "emqx_psk:lookup"
|
||||||
|
, converter => fun ?MODULE:parse_user_lookup_fun/1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
, {"secure_renegotiate",
|
, {"secure_renegotiate",
|
||||||
|
@ -946,6 +1014,45 @@ you drop support for the insecure renegotiation, prone to MitM attacks.
|
||||||
"""
|
"""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
].
|
||||||
|
|
||||||
|
%% @doc Make schema for SSL listener options.
|
||||||
|
%% When it's for ranch listener, an extra field `handshake_timeout' is added.
|
||||||
|
-spec server_ssl_opts_schema(map(), boolean()) -> hocon_schema:field_schema().
|
||||||
|
server_ssl_opts_schema(Defaults, IsRanchListener) ->
|
||||||
|
D = fun (Field) -> maps:get(to_atom(Field), Defaults, undefined) end,
|
||||||
|
Df = fun (Field, Default) -> maps:get(to_atom(Field), Defaults, Default) end,
|
||||||
|
common_ssl_opts_schema(Defaults) ++
|
||||||
|
[ {"dhfile",
|
||||||
|
sc(string(),
|
||||||
|
#{ default => D("dhfile")
|
||||||
|
, nullable => true
|
||||||
|
, desc =>
|
||||||
|
"""Path to a file containing PEM-encoded Diffie Hellman parameters
|
||||||
|
to be used by the server if a cipher suite using Diffie Hellman
|
||||||
|
key exchange is negotiated. If not specified, default parameters
|
||||||
|
are used.<br>
|
||||||
|
NOTE: The dhfile option is not supported by TLS 1.3."""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
, {"fail_if_no_peer_cert",
|
||||||
|
sc(boolean(),
|
||||||
|
#{ default => Df("fail_if_no_peer_cert", false)
|
||||||
|
, desc =>
|
||||||
|
"""
|
||||||
|
Used together with {verify, verify_peer} by an TLS/DTLS server.
|
||||||
|
If set to true, the server fails if the client does not have a
|
||||||
|
certificate to send, that is, sends an empty certificate.
|
||||||
|
If set to false, it fails only if the client sends an invalid
|
||||||
|
certificate (an empty certificate is considered valid).
|
||||||
|
"""
|
||||||
|
})
|
||||||
|
}
|
||||||
|
, {"honor_cipher_order",
|
||||||
|
sc(boolean(),
|
||||||
|
#{ default => Df("honor_cipher_order", true)
|
||||||
|
})
|
||||||
|
}
|
||||||
, {"client_renegotiation",
|
, {"client_renegotiation",
|
||||||
sc(boolean(),
|
sc(boolean(),
|
||||||
#{ default => Df("client_renegotiation", true)
|
#{ default => Df("client_renegotiation", true)
|
||||||
|
@ -961,72 +1068,104 @@ the number of messages the underlying cipher suite can encipher.
|
||||||
"""
|
"""
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
, {"reuse_sessions",
|
| [ {"handshake_timeout",
|
||||||
sc(boolean(),
|
|
||||||
#{ default => Df("reuse_sessions", true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"honor_cipher_order",
|
|
||||||
sc(boolean(),
|
|
||||||
#{ default => Df("honor_cipher_order", true)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"handshake_timeout",
|
|
||||||
sc(duration(),
|
sc(duration(),
|
||||||
#{ default => Df("handshake_timeout", "15s")
|
#{ default => Df("handshake_timeout", "15s")
|
||||||
})
|
, desc => "Maximum time duration allowed for the handshake to complete"
|
||||||
}
|
})}
|
||||||
, {"depth",
|
|| IsRanchListener]
|
||||||
sc(integer(),
|
|
||||||
#{default => Df("depth", 10)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"password",
|
|
||||||
sc(string(),
|
|
||||||
#{ default => D("key_password")
|
|
||||||
, sensitive => true
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"dhfile",
|
|
||||||
sc(string(),
|
|
||||||
#{ default => D("dhfile")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"server_name_indication",
|
|
||||||
sc(hoconsc:union([disable, string()]),
|
|
||||||
#{ default => D("server_name_indication")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"versions",
|
|
||||||
sc(typerefl:alias("string", list(atom())),
|
|
||||||
#{ default => maps:get(versions, Defaults, default_tls_vsns())
|
|
||||||
, converter => fun (Vsns) -> [tls_vsn(iolist_to_binary(V)) || V <- Vsns] end
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"ciphers",
|
|
||||||
sc(hoconsc:array(string()),
|
|
||||||
#{ default => D("ciphers")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
, {"user_lookup_fun",
|
|
||||||
sc(typerefl:alias("string", any()),
|
|
||||||
#{ default => "emqx_psk:lookup"
|
|
||||||
, converter => fun ?MODULE:parse_user_lookup_fun/1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
].
|
].
|
||||||
|
|
||||||
%% on erl23.2.7.2-emqx-2, sufficient_crypto_support('tlsv1.3') -> false
|
%% @doc Make schema for SSL client.
|
||||||
default_tls_vsns() -> [<<"tlsv1.2">>, <<"tlsv1.1">>, <<"tlsv1">>].
|
-spec client_ssl_opts_schema(map()) -> hocon_schema:field_schema().
|
||||||
|
client_ssl_opts_schema(Defaults) ->
|
||||||
|
common_ssl_opts_schema(Defaults) ++
|
||||||
|
[ { "server_name_indication",
|
||||||
|
sc(hoconsc:union([disable, string()]),
|
||||||
|
#{ default => disable
|
||||||
|
, desc =>
|
||||||
|
"""Specify the host name to be used in TLS Server Name Indication extension.<br>
|
||||||
|
For instance, when connecting to \"server.example.net\", the genuine server
|
||||||
|
which accedpts the connection and performs TSL handshake may differ from the
|
||||||
|
host the TLS client initially connects to, e.g. when connecting to an IP address
|
||||||
|
or when the host has multiple resolvable DNS records <br>
|
||||||
|
If not specified, it will default to the host name string which is used
|
||||||
|
to establish the connection, unless it is IP addressed used.<br>
|
||||||
|
The host name is then also used in the host name verification of the peer
|
||||||
|
certificate.<br> The special value 'disable' prevents the Server Name
|
||||||
|
Indication extension from being sent and disables the hostname
|
||||||
|
verification check."""
|
||||||
|
})}
|
||||||
|
].
|
||||||
|
|
||||||
tls_vsn(<<"tlsv1.3">>) -> 'tlsv1.3';
|
|
||||||
tls_vsn(<<"tlsv1.2">>) -> 'tlsv1.2';
|
|
||||||
tls_vsn(<<"tlsv1.1">>) -> 'tlsv1.1';
|
|
||||||
tls_vsn(<<"tlsv1">>) -> 'tlsv1'.
|
|
||||||
|
|
||||||
default_ciphers() -> [
|
default_tls_vsns(dtls) ->
|
||||||
"TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256", "TLS_CHACHA20_POLY1305_SHA256",
|
[<<"dtlsv1.2">>, <<"dtlsv1">>];
|
||||||
"TLS_AES_128_CCM_SHA256", "TLS_AES_128_CCM_8_SHA256", "ECDHE-ECDSA-AES256-GCM-SHA384",
|
default_tls_vsns(tcp) ->
|
||||||
|
[<<"tlsv1.3">>, <<"tlsv1.2">>, <<"tlsv1.1">>, <<"tlsv1">>].
|
||||||
|
|
||||||
|
-spec ciphers_schema(quic | dtls | tcp_all | undefined) -> hocon_schema:field_schema().
|
||||||
|
ciphers_schema(Default) ->
|
||||||
|
sc(hoconsc:array(string()),
|
||||||
|
#{ default => default_ciphers(Default)
|
||||||
|
, converter => fun(Ciphers) when is_binary(Ciphers) ->
|
||||||
|
binary:split(Ciphers, <<",">>, [global]);
|
||||||
|
(Ciphers) when is_list(Ciphers) ->
|
||||||
|
Ciphers
|
||||||
|
end
|
||||||
|
, validator => fun validate_ciphers/1
|
||||||
|
, desc =>
|
||||||
|
"""TLS cipher suite names separated by comma, or as an array of strings
|
||||||
|
<code>\"TLS_AES_256_GCM_SHA384,TLS_AES_128_GCM_SHA256\"</code> or
|
||||||
|
<code>[\"TLS_AES_256_GCM_SHA384\",\"TLS_AES_128_GCM_SHA256\"]</code].
|
||||||
|
<br>
|
||||||
|
Ciphers (and their ordering) define the way in which the
|
||||||
|
client and server encrypts information over the wire.
|
||||||
|
Selecting a good cipher suite is critical for the
|
||||||
|
application's data security, confidentiality and performance.
|
||||||
|
The names should be in OpenSSL sting format (not RFC format).
|
||||||
|
Default values and examples proveded by EMQ X config
|
||||||
|
documentation are all in OpenSSL format.<br>
|
||||||
|
|
||||||
|
NOTE: Certain cipher suites are only compatible with
|
||||||
|
specific TLS <code>versions</code> ('tlsv1.1', 'tlsv1.2' or 'tlsv1.3')
|
||||||
|
incompatible cipher suites will be silently dropped.
|
||||||
|
For instance, if only 'tlsv1.3' is given in the <code>versions</code>,
|
||||||
|
configuring cipher suites for other versions will have no effect.
|
||||||
|
<br>
|
||||||
|
|
||||||
|
NOTE: PSK ciphers are suppresed by 'tlsv1.3' version config<br>
|
||||||
|
If PSK cipher suites are intended, 'tlsv1.3' should be disabled from <code>versions</code>.<br>
|
||||||
|
PSK cipher suites: <code>\"RSA-PSK-AES256-GCM-SHA384,RSA-PSK-AES256-CBC-SHA384,
|
||||||
|
RSA-PSK-AES128-GCM-SHA256,RSA-PSK-AES128-CBC-SHA256,
|
||||||
|
RSA-PSK-AES256-CBC-SHA,RSA-PSK-AES128-CBC-SHA,
|
||||||
|
RSA-PSK-DES-CBC3-SHA,RSA-PSK-RC4-SHA\"</code><br>
|
||||||
|
""" ++ case Default of
|
||||||
|
quic -> "NOTE: QUIC listener supports only 'tlsv1.3' ciphers<br>";
|
||||||
|
_ -> ""
|
||||||
|
end}).
|
||||||
|
|
||||||
|
default_ciphers(undefined) ->
|
||||||
|
default_ciphers(tcp_all);
|
||||||
|
default_ciphers(quic) -> [
|
||||||
|
"TLS_AES_256_GCM_SHA384",
|
||||||
|
"TLS_AES_128_GCM_SHA256",
|
||||||
|
"TLS_CHACHA20_POLY1305_SHA256"
|
||||||
|
];
|
||||||
|
default_ciphers(tcp_all) ->
|
||||||
|
default_ciphers('tlsv1.3') ++
|
||||||
|
default_ciphers('tlsv1.2') ++
|
||||||
|
default_ciphers(psk);
|
||||||
|
default_ciphers(dtls) ->
|
||||||
|
%% as of now, dtls does not support tlsv1.3 ciphers
|
||||||
|
default_ciphers('tlsv1.2') ++ default_ciphers('psk');
|
||||||
|
default_ciphers('tlsv1.3') ->
|
||||||
|
["TLS_AES_256_GCM_SHA384", "TLS_AES_128_GCM_SHA256",
|
||||||
|
"TLS_CHACHA20_POLY1305_SHA256", "TLS_AES_128_CCM_SHA256",
|
||||||
|
"TLS_AES_128_CCM_8_SHA256"]
|
||||||
|
++ default_ciphers('tlsv1.2');
|
||||||
|
default_ciphers('tlsv1.2') -> [
|
||||||
|
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||||
"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA384",
|
"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA384",
|
||||||
"ECDHE-ECDSA-DES-CBC3-SHA", "ECDH-ECDSA-AES256-GCM-SHA384", "ECDH-RSA-AES256-GCM-SHA384",
|
"ECDHE-ECDSA-DES-CBC3-SHA", "ECDH-ECDSA-AES256-GCM-SHA384", "ECDH-RSA-AES256-GCM-SHA384",
|
||||||
"ECDH-ECDSA-AES256-SHA384", "ECDH-RSA-AES256-SHA384", "DHE-DSS-AES256-GCM-SHA384",
|
"ECDH-ECDSA-AES256-SHA384", "ECDH-RSA-AES256-SHA384", "DHE-DSS-AES256-GCM-SHA384",
|
||||||
|
@ -1039,10 +1178,12 @@ default_ciphers() -> [
|
||||||
"ECDH-ECDSA-AES256-SHA", "ECDH-RSA-AES256-SHA", "AES256-SHA", "ECDHE-ECDSA-AES128-SHA",
|
"ECDH-ECDSA-AES256-SHA", "ECDH-RSA-AES256-SHA", "AES256-SHA", "ECDHE-ECDSA-AES128-SHA",
|
||||||
"ECDHE-RSA-AES128-SHA", "DHE-DSS-AES128-SHA", "ECDH-ECDSA-AES128-SHA",
|
"ECDHE-RSA-AES128-SHA", "DHE-DSS-AES128-SHA", "ECDH-ECDSA-AES128-SHA",
|
||||||
"ECDH-RSA-AES128-SHA", "AES128-SHA"
|
"ECDH-RSA-AES128-SHA", "AES128-SHA"
|
||||||
] ++ psk_ciphers().
|
];
|
||||||
|
default_ciphers(psk) ->
|
||||||
psk_ciphers() -> [
|
[ "RSA-PSK-AES256-GCM-SHA384","RSA-PSK-AES256-CBC-SHA384",
|
||||||
"PSK-AES128-CBC-SHA", "PSK-AES256-CBC-SHA", "PSK-3DES-EDE-CBC-SHA", "PSK-RC4-SHA"
|
"RSA-PSK-AES128-GCM-SHA256","RSA-PSK-AES128-CBC-SHA256",
|
||||||
|
"RSA-PSK-AES256-CBC-SHA","RSA-PSK-AES128-CBC-SHA",
|
||||||
|
"RSA-PSK-DES-CBC3-SHA","RSA-PSK-RC4-SHA"
|
||||||
].
|
].
|
||||||
|
|
||||||
%% @private return a list of keys in a parent field
|
%% @private return a list of keys in a parent field
|
||||||
|
@ -1160,3 +1301,11 @@ parse_user_lookup_fun(StrConf) ->
|
||||||
Mod = list_to_atom(ModStr),
|
Mod = list_to_atom(ModStr),
|
||||||
Fun = list_to_atom(FunStr),
|
Fun = list_to_atom(FunStr),
|
||||||
{fun Mod:Fun/3, <<>>}.
|
{fun Mod:Fun/3, <<>>}.
|
||||||
|
|
||||||
|
validate_ciphers(Ciphers) ->
|
||||||
|
All = ssl:cipher_suites(all, 'tlsv1.3', openssl) ++
|
||||||
|
ssl:cipher_suites(all, 'tlsv1.2', openssl), %% includes older version ciphers
|
||||||
|
case lists:filter(fun(Cipher) -> not lists:member(Cipher, All) end, Ciphers) of
|
||||||
|
[] -> ok;
|
||||||
|
Bad -> {error, {bad_ciphers, Bad}}
|
||||||
|
end.
|
||||||
|
|
|
@ -0,0 +1,97 @@
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved.
|
||||||
|
%%
|
||||||
|
%% Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
%% you may not use this file except in compliance with the License.
|
||||||
|
%% You may obtain a copy of the License at
|
||||||
|
%%
|
||||||
|
%% http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
%%
|
||||||
|
%% Unless required by applicable law or agreed to in writing, software
|
||||||
|
%% distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
%% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
%% See the License for the specific language governing permissions and
|
||||||
|
%% limitations under the License.
|
||||||
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
|
-module(emqx_schema_tests).
|
||||||
|
|
||||||
|
-include_lib("eunit/include/eunit.hrl").
|
||||||
|
|
||||||
|
ssl_opts_dtls_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{versions => dtls,
|
||||||
|
ciphers => dtls}, false),
|
||||||
|
Checked = validate(Sc, #{<<"versions">> => [<<"dtlsv1.2">>, <<"dtlsv1">>]}),
|
||||||
|
?assertMatch(#{versions := ['dtlsv1.2', 'dtlsv1'],
|
||||||
|
ciphers := ["ECDHE-ECDSA-AES256-GCM-SHA384" | _]
|
||||||
|
}, Checked).
|
||||||
|
|
||||||
|
ssl_opts_tls_1_3_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
|
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>]}),
|
||||||
|
?assertNot(maps:is_key(handshake_timeout, Checked)),
|
||||||
|
?assertMatch(#{versions := ['tlsv1.3'],
|
||||||
|
ciphers := [_ | _]
|
||||||
|
}, Checked).
|
||||||
|
|
||||||
|
ssl_opts_tls_for_ranch_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, true),
|
||||||
|
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>]}),
|
||||||
|
?assertMatch(#{versions := ['tlsv1.3'],
|
||||||
|
ciphers := [_ | _],
|
||||||
|
handshake_timeout := _
|
||||||
|
}, Checked).
|
||||||
|
|
||||||
|
ssl_opts_cipher_array_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
|
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>],
|
||||||
|
<<"ciphers">> => [<<"TLS_AES_256_GCM_SHA384">>,
|
||||||
|
<<"ECDHE-ECDSA-AES256-GCM-SHA384">>]}),
|
||||||
|
?assertMatch(#{versions := ['tlsv1.3'],
|
||||||
|
ciphers := ["TLS_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384"]
|
||||||
|
}, Checked).
|
||||||
|
|
||||||
|
ssl_opts_cipher_comma_separated_string_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
|
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.3">>],
|
||||||
|
<<"ciphers">> => <<"TLS_AES_256_GCM_SHA384,ECDHE-ECDSA-AES256-GCM-SHA384">>}),
|
||||||
|
?assertMatch(#{versions := ['tlsv1.3'],
|
||||||
|
ciphers := ["TLS_AES_256_GCM_SHA384", "ECDHE-ECDSA-AES256-GCM-SHA384"]
|
||||||
|
}, Checked).
|
||||||
|
|
||||||
|
ssl_opts_tls_psk_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
|
Checked = validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>]}),
|
||||||
|
?assertMatch(#{versions := ['tlsv1.2']}, Checked),
|
||||||
|
#{ciphers := Ciphers} = Checked,
|
||||||
|
PskCiphers = emqx_schema:default_ciphers(psk),
|
||||||
|
lists:foreach(fun(Cipher) ->
|
||||||
|
?assert(lists:member(Cipher, Ciphers))
|
||||||
|
end, PskCiphers).
|
||||||
|
|
||||||
|
bad_cipher_test() ->
|
||||||
|
Sc = emqx_schema:server_ssl_opts_schema(#{}, false),
|
||||||
|
Reason = {bad_ciphers, ["foo"]},
|
||||||
|
?assertThrow({_Sc, [{validation_error, #{reason := Reason}}]},
|
||||||
|
[validate(Sc, #{<<"versions">> => [<<"tlsv1.2">>],
|
||||||
|
<<"ciphers">> => [<<"foo">>]})]),
|
||||||
|
ok.
|
||||||
|
|
||||||
|
validate(Schema, Data0) ->
|
||||||
|
Sc = #{ roots => [ssl_opts]
|
||||||
|
, fields => #{ssl_opts => Schema}
|
||||||
|
},
|
||||||
|
Data = Data0#{ cacertfile => <<"cacertfile">>
|
||||||
|
, certfile => <<"certfile">>
|
||||||
|
, keyfile => <<"keyfile">>
|
||||||
|
},
|
||||||
|
#{ssl_opts := Checked} =
|
||||||
|
hocon_schema:check_plain(Sc, #{<<"ssl_opts">> => Data},
|
||||||
|
#{atom_key => true}),
|
||||||
|
Checked.
|
||||||
|
|
||||||
|
ciperhs_schema_test() ->
|
||||||
|
Sc = emqx_schema:ciphers_schema(undefined),
|
||||||
|
WSc = #{roots => [{ciphers, Sc}]},
|
||||||
|
?assertThrow({_, [{validation_error, _}]},
|
||||||
|
hocon_schema:check_plain(WSc, #{<<"ciphers">> => <<"foo,bar">>})).
|
|
@ -1970,8 +1970,9 @@ find_config(AuthenticatorID, AuthenticatorsConfig) ->
|
||||||
end.
|
end.
|
||||||
|
|
||||||
fill_defaults(Config) ->
|
fill_defaults(Config) ->
|
||||||
#{<<"authentication">> := CheckedConfig} = hocon_schema:check_plain(
|
#{<<"authentication">> := CheckedConfig} =
|
||||||
?AUTHN, #{<<"authentication">> => Config}, #{no_conversion => true}),
|
hocon_schema:check_plain(?AUTHN, #{<<"authentication">> => Config},
|
||||||
|
#{only_fill_defaults => true}),
|
||||||
CheckedConfig.
|
CheckedConfig.
|
||||||
|
|
||||||
convert_certs(#{<<"ssl">> := SSLOpts} = Config) ->
|
convert_certs(#{<<"ssl">> := SSLOpts} = Config) ->
|
||||||
|
|
|
@ -53,18 +53,12 @@
|
||||||
|
|
||||||
-export([roots/0, fields/1]).
|
-export([roots/0, fields/1]).
|
||||||
|
|
||||||
roots() -> ["ssl"].
|
roots() -> [].
|
||||||
|
|
||||||
fields("ssl") ->
|
fields(_) -> [].
|
||||||
[ {enable, #{type => boolean(), default => false}}
|
|
||||||
, {cacertfile, fun cacertfile/1}
|
|
||||||
, {keyfile, fun keyfile/1}
|
|
||||||
, {certfile, fun certfile/1}
|
|
||||||
, {verify, fun verify/1}
|
|
||||||
].
|
|
||||||
|
|
||||||
ssl_fields() ->
|
ssl_fields() ->
|
||||||
[ {ssl, #{type => hoconsc:ref(?MODULE, "ssl"),
|
[ {ssl, #{type => hoconsc:ref(emqx_schema, ssl_client_opts),
|
||||||
default => #{<<"enable">> => false}
|
default => #{<<"enable">> => false}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,22 +100,6 @@ auto_reconnect(type) -> boolean();
|
||||||
auto_reconnect(default) -> true;
|
auto_reconnect(default) -> true;
|
||||||
auto_reconnect(_) -> undefined.
|
auto_reconnect(_) -> undefined.
|
||||||
|
|
||||||
cacertfile(type) -> string();
|
|
||||||
cacertfile(nullable) -> true;
|
|
||||||
cacertfile(_) -> undefined.
|
|
||||||
|
|
||||||
keyfile(type) -> string();
|
|
||||||
keyfile(nullable) -> true;
|
|
||||||
keyfile(_) -> undefined.
|
|
||||||
|
|
||||||
certfile(type) -> string();
|
|
||||||
certfile(nullable) -> true;
|
|
||||||
certfile(_) -> undefined.
|
|
||||||
|
|
||||||
verify(type) -> boolean();
|
|
||||||
verify(default) -> false;
|
|
||||||
verify(_) -> undefined.
|
|
||||||
|
|
||||||
servers(type) -> servers();
|
servers(type) -> servers();
|
||||||
servers(validator) -> [?NOT_EMPTY("the value of the field 'servers' cannot be empty")];
|
servers(validator) -> [?NOT_EMPTY("the value of the field 'servers' cannot be empty")];
|
||||||
servers(_) -> undefined.
|
servers(_) -> undefined.
|
||||||
|
|
|
@ -45,7 +45,9 @@ fields("http") ->
|
||||||
];
|
];
|
||||||
|
|
||||||
fields("https") ->
|
fields("https") ->
|
||||||
proplists:delete("fail_if_no_peer_cert", emqx_schema:ssl(#{})) ++ fields("http").
|
fields("http") ++
|
||||||
|
proplists:delete("fail_if_no_peer_cert",
|
||||||
|
emqx_schema:server_ssl_opts_schema(#{}, true)).
|
||||||
|
|
||||||
default_username(type) -> string();
|
default_username(type) -> string();
|
||||||
default_username(default) -> "admin";
|
default_username(default) -> "admin";
|
||||||
|
|
|
@ -163,7 +163,9 @@ fields(tcp_listener) ->
|
||||||
|
|
||||||
fields(ssl_listener) ->
|
fields(ssl_listener) ->
|
||||||
fields(tcp_listener) ++
|
fields(tcp_listener) ++
|
||||||
ssl_opts();
|
[{ssl, sc_meta(hoconsc:ref(emqx_schema, "listener_ssl_opts"),
|
||||||
|
#{desc => "SSL listener options"})}];
|
||||||
|
|
||||||
|
|
||||||
fields(udp_listener) ->
|
fields(udp_listener) ->
|
||||||
[
|
[
|
||||||
|
@ -174,7 +176,8 @@ fields(udp_listener) ->
|
||||||
|
|
||||||
fields(dtls_listener) ->
|
fields(dtls_listener) ->
|
||||||
fields(udp_listener) ++
|
fields(udp_listener) ++
|
||||||
dtls_opts();
|
[{dtls, sc_meta(ref(dtls_opts),
|
||||||
|
#{desc => "DTLS listener options"})}];
|
||||||
|
|
||||||
fields(udp_opts) ->
|
fields(udp_opts) ->
|
||||||
[ {active_n, sc(integer(), 100)}
|
[ {active_n, sc(integer(), 100)}
|
||||||
|
@ -184,45 +187,13 @@ fields(udp_opts) ->
|
||||||
, {reuseaddr, sc(boolean(), true)}
|
, {reuseaddr, sc(boolean(), true)}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(dtls_listener_ssl_opts) ->
|
fields(dtls_opts) ->
|
||||||
Base = emqx_schema:fields("listener_ssl_opts"),
|
emqx_schema:server_ssl_opts_schema(
|
||||||
DtlsVers = hoconsc:mk(
|
#{ depth => 10
|
||||||
typerefl:alias("string", list(atom())),
|
, reuse_sessions => true
|
||||||
#{ default => default_dtls_vsns(),
|
, versions => dtls
|
||||||
converter => fun (Vsns) ->
|
, ciphers => dtls
|
||||||
[dtls_vsn(iolist_to_binary(V)) || V <- Vsns]
|
}, false).
|
||||||
end
|
|
||||||
}),
|
|
||||||
Ciphers = sc(hoconsc:array(string()), default_ciphers()),
|
|
||||||
lists:keydelete(
|
|
||||||
"handshake_timeout", 1,
|
|
||||||
lists:keyreplace(
|
|
||||||
"ciphers", 1,
|
|
||||||
lists:keyreplace("versions", 1, Base, {"versions", DtlsVers}),
|
|
||||||
{"ciphers", Ciphers}
|
|
||||||
)
|
|
||||||
).
|
|
||||||
|
|
||||||
default_ciphers() ->
|
|
||||||
["ECDHE-ECDSA-AES256-GCM-SHA384",
|
|
||||||
"ECDHE-RSA-AES256-GCM-SHA384", "ECDHE-ECDSA-AES256-SHA384", "ECDHE-RSA-AES256-SHA384",
|
|
||||||
"ECDHE-ECDSA-DES-CBC3-SHA", "ECDH-ECDSA-AES256-GCM-SHA384", "ECDH-RSA-AES256-GCM-SHA384",
|
|
||||||
"ECDH-ECDSA-AES256-SHA384", "ECDH-RSA-AES256-SHA384", "DHE-DSS-AES256-GCM-SHA384",
|
|
||||||
"DHE-DSS-AES256-SHA256", "AES256-GCM-SHA384", "AES256-SHA256",
|
|
||||||
"ECDHE-ECDSA-AES128-GCM-SHA256", "ECDHE-RSA-AES128-GCM-SHA256",
|
|
||||||
"ECDHE-ECDSA-AES128-SHA256", "ECDHE-RSA-AES128-SHA256", "ECDH-ECDSA-AES128-GCM-SHA256",
|
|
||||||
"ECDH-RSA-AES128-GCM-SHA256", "ECDH-ECDSA-AES128-SHA256", "ECDH-RSA-AES128-SHA256",
|
|
||||||
"DHE-DSS-AES128-GCM-SHA256", "DHE-DSS-AES128-SHA256", "AES128-GCM-SHA256", "AES128-SHA256",
|
|
||||||
"ECDHE-ECDSA-AES256-SHA", "ECDHE-RSA-AES256-SHA", "DHE-DSS-AES256-SHA",
|
|
||||||
"ECDH-ECDSA-AES256-SHA", "ECDH-RSA-AES256-SHA", "AES256-SHA", "ECDHE-ECDSA-AES128-SHA",
|
|
||||||
"ECDHE-RSA-AES128-SHA", "DHE-DSS-AES128-SHA", "ECDH-ECDSA-AES128-SHA",
|
|
||||||
"ECDH-RSA-AES128-SHA", "AES128-SHA"
|
|
||||||
] ++ psk_ciphers().
|
|
||||||
|
|
||||||
psk_ciphers() ->
|
|
||||||
["PSK-AES128-CBC-SHA", "PSK-AES256-CBC-SHA",
|
|
||||||
"PSK-3DES-EDE-CBC-SHA", "PSK-RC4-SHA"
|
|
||||||
].
|
|
||||||
|
|
||||||
% authentication() ->
|
% authentication() ->
|
||||||
% hoconsc:union(
|
% hoconsc:union(
|
||||||
|
@ -270,23 +241,11 @@ tcp_opts() ->
|
||||||
udp_opts() ->
|
udp_opts() ->
|
||||||
[{udp, sc_meta(ref(udp_opts), #{})}].
|
[{udp, sc_meta(ref(udp_opts), #{})}].
|
||||||
|
|
||||||
ssl_opts() ->
|
|
||||||
[{ssl, sc_meta(ref(emqx_schema, "listener_ssl_opts"), #{})}].
|
|
||||||
|
|
||||||
dtls_opts() ->
|
|
||||||
[{dtls, sc_meta(ref(dtls_listener_ssl_opts), #{})}].
|
|
||||||
|
|
||||||
proxy_protocol_opts() ->
|
proxy_protocol_opts() ->
|
||||||
[ {proxy_protocol, sc(boolean())}
|
[ {proxy_protocol, sc(boolean())}
|
||||||
, {proxy_protocol_timeout, sc(duration())}
|
, {proxy_protocol_timeout, sc(duration())}
|
||||||
].
|
].
|
||||||
|
|
||||||
default_dtls_vsns() ->
|
|
||||||
[<<"dtlsv1.2">>, <<"dtlsv1">>].
|
|
||||||
|
|
||||||
dtls_vsn(<<"dtlsv1.2">>) -> 'dtlsv1.2';
|
|
||||||
dtls_vsn(<<"dtlsv1">>) -> 'dtlsv1'.
|
|
||||||
|
|
||||||
sc(Type) ->
|
sc(Type) ->
|
||||||
sc_meta(Type, #{}).
|
sc_meta(Type, #{}).
|
||||||
|
|
||||||
|
|
|
@ -211,13 +211,10 @@ fields(cluster_etcd) ->
|
||||||
#{ default => "1m"
|
#{ default => "1m"
|
||||||
})}
|
})}
|
||||||
, {"ssl",
|
, {"ssl",
|
||||||
sc(ref(etcd_ssl_opts),
|
sc(hoconsc:ref(emqx_schema, ssl_client_opts),
|
||||||
#{})}
|
#{})}
|
||||||
];
|
];
|
||||||
|
|
||||||
fields(etcd_ssl_opts) ->
|
|
||||||
emqx_schema:ssl(#{});
|
|
||||||
|
|
||||||
fields(cluster_k8s) ->
|
fields(cluster_k8s) ->
|
||||||
[ {"apiserver",
|
[ {"apiserver",
|
||||||
sc(string(),
|
sc(string(),
|
||||||
|
@ -312,23 +309,7 @@ fields("node") ->
|
||||||
)}
|
)}
|
||||||
, {"etc_dir",
|
, {"etc_dir",
|
||||||
sc(string(),
|
sc(string(),
|
||||||
#{
|
#{ desc => "`etc` dir for the node"
|
||||||
converter => fun(EtcDir) ->
|
|
||||||
case filename:absname(EtcDir) =:= EtcDir of
|
|
||||||
true ->
|
|
||||||
unicode:characters_to_list(EtcDir);
|
|
||||||
false ->
|
|
||||||
unicode:characters_to_list(filename:join([code:lib_dir(), "..", EtcDir]))
|
|
||||||
end
|
|
||||||
end,
|
|
||||||
validator => fun(Path) ->
|
|
||||||
case filelib:is_dir(Path) of
|
|
||||||
true ->
|
|
||||||
ok;
|
|
||||||
false ->
|
|
||||||
error({not_dir, Path})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
}
|
}
|
||||||
)}
|
)}
|
||||||
];
|
];
|
||||||
|
|
|
@ -44,7 +44,7 @@
|
||||||
|
|
||||||
{deps,
|
{deps,
|
||||||
[ {gpb, "4.11.2"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps
|
[ {gpb, "4.11.2"} %% gpb only used to build, but not for release, pin it here to avoid fetching a wrong version due to rebar plugins scattered in all the deps
|
||||||
, {typerefl, {git, "https://github.com/k32/typerefl", {tag, "0.8.4"}}}
|
, {typerefl, {git, "https://github.com/k32/typerefl", {tag, "0.8.5"}}}
|
||||||
, {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.9"}}}
|
, {ehttpc, {git, "https://github.com/emqx/ehttpc", {tag, "0.1.9"}}}
|
||||||
, {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
|
, {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.8.0"}}}
|
||||||
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
, {jiffy, {git, "https://github.com/emqx/jiffy", {tag, "1.0.5"}}}
|
||||||
|
@ -61,7 +61,7 @@
|
||||||
, {observer_cli, "1.7.1"} % NOTE: depends on recon 2.5.x
|
, {observer_cli, "1.7.1"} % NOTE: depends on recon 2.5.x
|
||||||
, {getopt, "1.0.2"}
|
, {getopt, "1.0.2"}
|
||||||
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
, {snabbkaffe, {git, "https://github.com/kafka4beam/snabbkaffe.git", {tag, "0.14.1"}}}
|
||||||
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.19.0"}}}
|
, {hocon, {git, "https://github.com/emqx/hocon.git", {tag, "0.19.5"}}}
|
||||||
, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.4.1"}}}
|
, {emqx_http_lib, {git, "https://github.com/emqx/emqx_http_lib.git", {tag, "0.4.1"}}}
|
||||||
, {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.2.0"}}}
|
, {esasl, {git, "https://github.com/emqx/esasl", {tag, "0.2.0"}}}
|
||||||
, {jose, {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}}
|
, {jose, {git, "https://github.com/potatosalad/erlang-jose", {tag, "1.11.1"}}}
|
||||||
|
|
Loading…
Reference in New Issue