diff --git a/doc/configuration/outgoing-connections.md b/doc/configuration/outgoing-connections.md index a9c858612d6..14cdb56240b 100644 --- a/doc/configuration/outgoing-connections.md +++ b/doc/configuration/outgoing-connections.md @@ -195,7 +195,7 @@ Logical database index (zero-based). ## Riak options -Currently only one Riak connection pool can exist for each supported XMPP host (the default pool). +Currently, only one Riak connection pool can exist for each supported XMPP host (the default pool). !!! WARNING `riak` backend is not compatible with `available_worker` strategy. @@ -245,7 +245,7 @@ Cassandra also supports all TLS-specific options described in the TLS section. ## Elasticsearch options -Currently only one pool tagged `default` can be used. +Currently, only one pool tagged `default` can be used. ### `outgoing_pools.elastic.default.connection.host` * **Syntax:** string @@ -363,7 +363,7 @@ Reconnect interval after a failed connection. * **Default:** `"none"` * **Example:** `encrypt = "tls"` -LDAP also supports all TLS-specific options described in the TLS section (provided `encrypt` is set to `tls`). +LDAP also supports all TLS-specific options described in the TLS section (provided `encrypt` is set to `tls`). ## TLS options @@ -435,7 +435,14 @@ Cipher suites to use. For allowed values, see the [Erlang/OTP SSL documentation] ### `outgoing_pools.*.*.connection.tls.server_name_indication` * **Syntax:** boolean -* **Default:** `true` +* **Default:** `false`, but enabled if the `verify_peer` option is set to `true` * **Example:** `tls.server_name_indication = false` -Enables SNI extension to TLS protocol. +Enables SNI extension to TLS protocol. If set to `true`, the `server_name_indication_host` option should be provided. + +### `outgoing_pools.*.*.connection.tls.server_name_indication_host` +* **Syntax:** string +* **Default:** not set +* **Example:** `tls.server_name_indication_host = "domain.com"` + +Domain against which the certificates will be checked, using SNI. It can be specified only when `server_name_indication` is set to `true`. diff --git a/src/config/mongoose_config_spec.erl b/src/config/mongoose_config_spec.erl index f60c5310e9a..bcb9533d244 100644 --- a/src/config/mongoose_config_spec.erl +++ b/src/config/mongoose_config_spec.erl @@ -15,7 +15,7 @@ process_ctl_access_rule/1, process_listener/2, process_verify_peer/1, - process_sni/1, + process_tls_sni/1, process_xmpp_tls/1, process_fast_tls/1, process_http_handler/2, @@ -361,8 +361,7 @@ c2s_tls() -> validate = non_empty}, wrap = {kv, crlfiles}}, <<"password">> => #option{type = string}, - <<"server_name_indication">> => #option{type = boolean, - process = fun ?MODULE:process_sni/1}, + <<"server_name_indication">> => #option{type = boolean}, <<"versions">> => #list{items = #option{type = atom}} }, process = fun ?MODULE:process_xmpp_tls/1 @@ -389,7 +388,8 @@ http_listener_tls() -> items = Items#{<<"verify_mode">> => #option{type = atom, validate = {enum, [peer, selfsigned_peer, none]}} }, - wrap = {kv, ssl} + wrap = {kv, ssl}, + process = fun ?MODULE:process_tls_sni/1 }. %% path: listen.http[].transport @@ -562,7 +562,8 @@ outgoing_pool_connection(<<"cassandra">>) -> required = all, process = fun ?MODULE:process_cassandra_auth/1}, <<"tls">> => #section{items = tls_items(), - wrap = {kv, ssl}} + wrap = {kv, ssl}, + process = fun ?MODULE:process_tls_sni/1} } }; outgoing_pool_connection(<<"elastic">>) -> @@ -583,7 +584,8 @@ outgoing_pool_connection(<<"http">>) -> <<"request_timeout">> => #option{type = integer, validate = non_negative}, <<"tls">> => #section{items = tls_items(), - wrap = {kv, http_opts}} + wrap = {kv, http_opts}, + process = fun ?MODULE:process_tls_sni/1} } }; outgoing_pool_connection(<<"ldap">>) -> @@ -600,7 +602,8 @@ outgoing_pool_connection(<<"ldap">>) -> <<"connect_interval">> => #option{type = integer, validate = positive}, <<"tls">> => #section{items = tls_items(), - wrap = {kv, tls_options}} + wrap = {kv, tls_options}, + process = fun ?MODULE:process_tls_sni/1} } }; outgoing_pool_connection(<<"rabbit">>) -> @@ -700,8 +703,9 @@ riak_credentials() -> sql_tls() -> Items = tls_items(), #section{ - items = Items#{<<"required">> => #option{type = boolean}} - }. + items = Items#{<<"required">> => #option{type = boolean}}, + process = fun ?MODULE:process_tls_sni/1 + }. tls_items() -> #{<<"verify_peer">> => #option{type = boolean, @@ -716,8 +720,9 @@ tls_items() -> <<"keyfile">> => #option{type = string, validate = non_empty}, <<"password">> => #option{type = string}, - <<"server_name_indication">> => #option{type = boolean, - process = fun ?MODULE:process_sni/1}, + <<"server_name_indication">> => #option{type = boolean}, + <<"server_name_indication_host">> => #option{type = string, + validate = non_empty}, <<"ciphers">> => #option{type = string}, <<"versions">> => #list{items = #option{type = atom}} }. @@ -1032,9 +1037,6 @@ get_all_hosts_and_host_types(General) -> [] end, General). -process_sni(false) -> - disable. - process_verify_peer(false) -> verify_none; process_verify_peer(true) -> verify_peer. @@ -1046,7 +1048,8 @@ process_xmpp_tls(KVs) -> end. tls_keys(just_tls) -> - [verify_mode, disconnect_on_failure, crlfiles, password, server_name_indication, versions]; + [verify_mode, disconnect_on_failure, crlfiles, password, server_name_indication, + server_name_indication_host, versions]; tls_keys(fast_tls) -> [protocol_options]. @@ -1054,7 +1057,8 @@ common_tls_keys() -> [module, mode, verify_peer, certfile, cacertfile, dhfile, ciphers]. process_xmpp_tls(just_tls, KVs) -> - {[VM, DoF], Opts} = proplists:split(KVs, [verify_mode, disconnect_on_failure]), + KVsWithSNI = process_tls_sni(KVs), + {[VM, DoF], Opts} = proplists:split(KVsWithSNI, [verify_mode, disconnect_on_failure]), {External, Internal} = lists:partition(fun is_external_tls_opt/1, Opts), SSLOpts = ssl_opts(verify_fun(VM, DoF) ++ Internal), [{tls_module, just_tls}] ++ SSLOpts ++ External; @@ -1206,12 +1210,27 @@ ssl_opts(pgsql, Opts) -> [{ssl_opts, Opts}]; ssl_opts(mysql, Opts) -> Opts. process_riak_tls(KVs) -> - {[CACertFileOpts], SSLOpts} = proplists:split(KVs, [cacertfile]), + KVsWithSNI = process_tls_sni(KVs), + {[CACertFileOpts], SSLOpts} = proplists:split(KVsWithSNI, [cacertfile]), riak_ssl(SSLOpts) ++ CACertFileOpts. riak_ssl([]) -> []; riak_ssl(Opts) -> [{ssl_opts, Opts}]. +process_tls_sni(KVs) -> + % the SSL library expects either the atom `disable` or a string with the SNI host + % as value for `server_name_indication` + SNIKeys = [server_name_indication, server_name_indication_host], + {[SNIOpt, SNIHostOpt], SSLOpts} = proplists:split(KVs, SNIKeys), + case {SNIOpt, SNIHostOpt} of + {[], []} -> + SSLOpts; + {[{server_name_indication, false}], _} -> + [{server_name_indication, disable}] ++ SSLOpts; + {[{server_name_indication, true}], [{server_name_indication_host, SNIHost}]} -> + [{server_name_indication, SNIHost}] ++ SSLOpts + end. + process_riak_credentials(KVs) -> {[[{user, User}], [{password, Pass}]], []} = proplists:split(KVs, [user, password]), {User, Pass}. diff --git a/test/config_parser_SUITE.erl b/test/config_parser_SUITE.erl index 6503f1de1c4..2c0f92c32a9 100644 --- a/test/config_parser_SUITE.erl +++ b/test/config_parser_SUITE.erl @@ -1161,6 +1161,31 @@ pool_http_request_timeout(_Config) -> pool_http_tls(_Config) -> ?cfg(pool_config({http, global, default, [], [{http_opts, [{certfile, "cert.pem"} ]}]}), pool_conn_raw(<<"http">>, #{<<"tls">> => #{<<"certfile">> => <<"cert.pem">>}})), + ?cfg(pool_config({http, global, default, [], [{http_opts, [{certfile, "cert.pem"}, + {verify, verify_peer}, + {cacertfile, "priv/ca.pem"}, + {server_name_indication, disable}]}]}), + pool_conn_raw(<<"http">>, #{<<"tls">> => #{<<"certfile">> => <<"cert.pem">>, + <<"verify_peer">> => true, + <<"cacertfile">> => <<"priv/ca.pem">>, + <<"server_name_indication">> => false}})), + ?cfg(pool_config({http, global, default, [], [{http_opts, [{certfile, "cert.pem"}, + {verify, verify_peer}, + {cacertfile, "priv/ca.pem"}, + {server_name_indication, "domain.com"}]}]}), + pool_conn_raw(<<"http">>, #{<<"tls">> => #{<<"certfile">> => <<"cert.pem">>, + <<"verify_peer">> => true, + <<"cacertfile">> => <<"priv/ca.pem">>, + <<"server_name_indication">> => true, + <<"server_name_indication_host">> => <<"domain.com">>}})), + ?cfg(pool_config({http, global, default, [], [{http_opts, [{verify, verify_peer}, + {cacertfile, "priv/ca.pem"}]}]}), + pool_conn_raw(<<"http">>, #{<<"tls">> => #{<<"verify_peer">> => true, + <<"cacertfile">> => <<"priv/ca.pem">>}})), + ?err(pool_conn_raw(<<"http">>, #{<<"tls">> => #{<<"verify_peer">> => true, + <<"cacertfile">> => <<"priv/ca.pem">>, + <<"server_name_indication">> => <<"domain.com">>, + <<"server_name_indication_host">> => <<"domain.com">>}})), ?err(pool_conn_raw(<<"http">>, #{<<"tls">> => #{<<"certfile">> => true}})), ?err(pool_conn_raw(<<"http">>, #{<<"tls">> => <<"secure">>})). @@ -3239,6 +3264,8 @@ handle_listener(V1, V2) -> handle_listener_option({tls, O1}, {tls, O2}) -> compare_unordered_lists(O1, O2); +handle_listener_option({ssl, O1}, {ssl, O2}) -> + compare_unordered_lists(O1, O2); handle_listener_option({modules, M1}, {modules, M2}) -> compare_unordered_lists(M1, M2, fun handle_listener_module/2); handle_listener_option({transport_options, O1}, {transport_options, O2}) -> @@ -3283,6 +3310,8 @@ handle_conn_opt({server, {D1, H1, DB1, U1, P1, O1}}, ?eq(U1, U2), ?eq(P1, P2), compare_unordered_lists(O1, O2, fun handle_db_server_opt/2); +handle_conn_opt({http_opts, O1}, {http_opts, O2}) -> + compare_unordered_lists(O1, O2); handle_conn_opt(V1, V2) -> ?eq(V1, V2). handle_db_server_opt({ssl_opts, O1}, {ssl_opts, O2}) ->