MongooseIM
MongooseIM copied to clipboard
Configuring SSL on listeners with multiple hosts
MongooseIM version: 3.2.0 Installed from: source Erlang/OTP version: 21
This is probably just my ignorance but I haven't been able to get listeners working on multiple SSL enabled hosts. I have them defined under host_config blocks like so:
{host_config, "dev.example.com", [
{listen, [
%% BOSH and WS endpoints over HTTPS
{ 5285, ejabberd_cowboy, [
{num_acceptors, 10},
{transport_options, [{max_connections, 1024}]},
{ssl, [
{certfile, "/path/to/certs/dev.example.com.crt"},
{keyfile, "/path/to/keys/dev.example.com.key"}
]},
{modules, [
{"_", "/http-bind", mod_bosh},
{"_", "/ws-xmpp", mod_websockets, []}
]}
]},
{ 8089 , ejabberd_cowboy, [
{num_acceptors, 10},
{transport_options, [{max_connections, 1024}]},
{protocol_options, [{compress, true}]},
{ssl, [
{certfile, "/path/to/certs/dev.example.com.crt"},
{keyfile, "/path/to/keys/dev.example.com.key"}
]},
{modules, [
{"_", "/api/sse", lasse_handler, [mongoose_client_api_sse]},
{"_", "/api/messages/[:with]", mongoose_client_api_messages, []},
{"_", "/api/contacts/[:jid]", mongoose_client_api_contacts, []},
{"_", "/api/rooms/[:id]", mongoose_client_api_rooms, []},
{"_", "/api/rooms/[:id]/config", mongoose_client_api_rooms_config, []},
{"_", "/api/rooms/:id/users/[:user]", mongoose_client_api_rooms_users, []},
{"_", "/api/rooms/[:id]/messages", mongoose_client_api_rooms_messages, []}
]}
]}
]}
]}.
{host_config, "example.com", [
{listen, [
%% BOSH and WS endpoints over HTTPS
{ 5285, ejabberd_cowboy, [
{num_acceptors, 10},
{transport_options, [{max_connections, 1024}]},
{ssl, [
{certfile, "/path/to/certs/example.com.crt"},
{keyfile, "/path/to/keys/example.com.key"}
]},
{modules, [
{"_", "/http-bind", mod_bosh},
{"_", "/ws-xmpp", mod_websockets, []}
]}
]},
{ 8089 , ejabberd_cowboy, [
{num_acceptors, 10},
{transport_options, [{max_connections, 1024}]},
{protocol_options, [{compress, true}]},
{ssl, [
{certfile, "/path/to/certs/example.com.crt"},
{keyfile, "/path/to/keys/example.com.key"}
]},
{modules, [
{"_", "/api/sse", lasse_handler, [mongoose_client_api_sse]},
{"_", "/api/messages/[:with]", mongoose_client_api_messages, []},
{"_", "/api/contacts/[:jid]", mongoose_client_api_contacts, []},
{"_", "/api/rooms/[:id]", mongoose_client_api_rooms, []},
{"_", "/api/rooms/[:id]/config", mongoose_client_api_rooms_config, []},
{"_", "/api/rooms/:id/users/[:user]", mongoose_client_api_rooms_users, []},
{"_", "/api/rooms/[:id]/messages", mongoose_client_api_rooms_messages, []}
]}
]}
]}
]}.
That didn't seem to work. Is this the correct approach?
Hi @cogentParadigm
I admit I use host_config very rarely so let's begin with general question. What is the incorrect behaviour exactly? No listeners started at all? Only some of them started?
@fenek
Thanks for the reply. I'm pretty sure no listeners are started from a host_config. I tried it with and without global listeners. When I have global listeners, only the global ones are started and the host_config does not override anything. If I don't have any global listeners configured, I have no listeners at all. So I'm guessing host_config is simply not supported with listeners. I hope there is another way to set unique certificates. My desired configurations are otherwise the same per host, it's just the SSL certificates that need to vary by host.
I've checked the source code and there's indeed a mistake in our documentation. listen option is not used at all, if provided inside host_config. However, even if it were, configuration as you provided in original post wouldn't work, because there is no logic inside listener code to use different certificate based on domain.
This is an interesting area to explore and I'm going to add it to our backlog. However, it's not a trivial modification to the source code so I can't promise when it will be implemented.
Thanks for looking into it. I'm going to try and work around this for now by using subdomains with a wildcard certificate. However, it's likely I will eventually need different domains so thanks for adding it to your backlog.
I figured this out. The ssl options are passed to ranch_ssl (more details here: https://ninenines.eu/docs/en/ranch/1.7/manual/ranch_ssl/)
ranch_ssl has two parameters, sni_hosts and sni_fun, which make it possible to configure this without needing host specific listeners.
Here's an example using sni_hosts which just allows you to pass host specific configurations:
{listen, [
%% BOSH and WS endpoints over HTTPS
{ 5285, ejabberd_cowboy, [
{num_acceptors, 10},
{transport_options, [{max_connections, 1024}]},
{ssl, [
{certfile, "/path/to/default/server.crt"},
{keyfile, "/path/to/default/server.key"},
{sni_hosts, [
{"dev.example.com", [
{certfile, "/path/to/dev.example.com/server.crt"},
{keyfile, "/path/to/dev.example.com/server.key"}
]},
{"stage.example.com", [
{certfile, "/path/to/stage.example.com/server.crt"},
{keyfile, "/path/to/stage.example.com/server.key"}
]}
]}
]},
{modules, [
{"_", "/http-bind", mod_bosh},
{"_", "/ws-xmpp", mod_websockets, []}
]}
]}
]}.
Alternatively, if you cannot use a static path, for example if you have an auto renewal system that changes your certificate paths, you can use sni_fun instead to specify a function like so:
{listen, [
%% BOSH and WS endpoints over HTTPS
{ 5285, ejabberd_cowboy, [
{num_acceptors, 10},
{transport_options, [{max_connections, 1024}]},
{ssl, [
{certfile, "/path/to/default/server.crt"},
{keyfile, "/path/to/default/server.key"},
{sni_fun, fun sni_helper:sni_fun/1}
]},
{modules, [
{"_", "/http-bind", mod_bosh},
{"_", "/ws-xmpp", mod_websockets, []}
]}
]}
]}.
Here is an example implementation of the sni_helper module which just gets the newest files in the given directories:
module(sni_helper).
%% API
-export([sni_fun/1]).
sni_fun(ServerName) ->
case ServerName of
"stage.example.com" ->
CertPath = string:strip(os:cmd("ls -t /path/to/stage.example.com/certs/* | head -1"), both, $\n),
KeyPath = string:strip(os:cmd("ls -t /path/to/stage.example.com/keys/* | head -1"), both, $\n),
[
{certfile, CertPath},
{keyfile, KeyPath}
];
"dev.example.com" ->
CertPath = string:strip(os:cmd("ls -t /path/to/dev.example.com/certs/* | head -1"), both, $\n),
KeyPath = string:strip(os:cmd("ls -t /path/to/dev.example.com/keys/* | head -1"), both, $\n),
[
{certfile, CertPath},
{keyfile, KeyPath}
];
_ ->
[
{certfile, "/path/to/default/server.crt"},
{keyfile, "/path/to/default/server.key"}
]
end.
Have you progressed on this ticket / feature?