Bridge via TLS

This commit is contained in:
terry-xiaoyu 2018-11-08 00:20:59 +08:00 committed by turtleDeng
parent 3879dcdf59
commit 2dc8ec8b11
5 changed files with 53 additions and 21 deletions

View File

@ -1672,21 +1672,25 @@ bridge.aws.mqueue_type = memory
## Value: Number ## Value: Number
bridge.aws.max_pending_messages = 10000 bridge.aws.max_pending_messages = 10000
## Bribge to remote server via SSL.
##
## Value: on | off
bridge.aws.ssl = off
## PEM-encoded CA certificates of the bridge. ## PEM-encoded CA certificates of the bridge.
## ##
## Value: File ## Value: File
## bridge.aws.cacertfile = cacert.pem ## bridge.aws.cacertfile = {{ platform_etc_dir }}/certs/cacert.pem
## SSL Certfile of the bridge. ## SSL Certfile of the bridge.
## ##
## Value: File ## Value: File
## bridge.aws.certfile = cert.pem ## bridge.aws.certfile = {{ platform_etc_dir }}/certs/client-cert.pem
## SSL Keyfile of the bridge. ## SSL Keyfile of the bridge.
## ##
## Value: File ## Value: File
## bridge.aws.keyfile = key.pem ## bridge.aws.keyfile = {{ platform_etc_dir }}/certs/client-key.pem
## SSL Ciphers used by the bridge. ## SSL Ciphers used by the bridge.
## ##

View File

@ -1552,6 +1552,11 @@ end}.
{datatype, string} {datatype, string}
]}. ]}.
{mapping, "bridge.$name.ssl", "emqx.bridges", [
{datatype, flag},
{default, off}
]}.
{mapping, "bridge.$name.cacertfile", "emqx.bridges", [ {mapping, "bridge.$name.cacertfile", "emqx.bridges", [
{datatype, string} {datatype, string}
]}. ]}.
@ -1575,11 +1580,12 @@ end}.
{mapping, "bridge.$name.keepalive", "emqx.bridges", [ {mapping, "bridge.$name.keepalive", "emqx.bridges", [
{default, "10s"}, {default, "10s"},
{datatype, {duration, s}} {datatype, {duration, ms}}
]}. ]}.
{mapping, "bridge.$name.tls_versions", "emqx.bridges", [ {mapping, "bridge.$name.tls_versions", "emqx.bridges", [
{datatype, string} {datatype, string},
{default, "tlsv1,tlsv1.1,tlsv1.2"}
]}. ]}.
{mapping, "bridge.$name.subscription.$id.topic", "emqx.bridges", [ {mapping, "bridge.$name.subscription.$id.topic", "emqx.bridges", [
@ -1597,12 +1603,11 @@ end}.
{mapping, "bridge.$name.reconnect_interval", "emqx.bridges", [ {mapping, "bridge.$name.reconnect_interval", "emqx.bridges", [
{default, "30s"}, {default, "30s"},
{datatype, {duration, s}} {datatype, {duration, ms}}
]}. ]}.
{translation, "emqx.bridges", fun(Conf) -> {translation, "emqx.bridges", fun(Conf) ->
Split = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end, Split = fun(undefined) -> undefined; (S) -> string:tokens(S, ",") end,
IsSsl = fun(cacertfile) -> true; IsSsl = fun(cacertfile) -> true;
@ -1625,11 +1630,12 @@ end}.
case IsSsl(Opt) of case IsSsl(Opt) of
true -> true ->
SslOpts = [Parse(Opt, Val)|proplists:get_value(ssl_opts, Opts, [])], SslOpts = [Parse(Opt, Val)|proplists:get_value(ssl_opts, Opts, [])],
lists:ukeymerge(1, [{ssl_opts, SslOpts}], Opts); lists:ukeymerge(1, [{ssl_opts, SslOpts}], lists:usort(Opts));
false -> false ->
[{Opt, Val}|Opts] [{Opt, Val}|Opts]
end end
end, end,
Subscriptions = fun(Name) -> Subscriptions = fun(Name) ->
Configs = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".subscription", Conf), Configs = cuttlefish_variable:filter_by_prefix("bridge." ++ Name ++ ".subscription", Conf),
lists:zip([Topic || {_, Topic} <- lists:sort([{I, Topic} || {[_, _, "subscription", I, "topic"], Topic} <- Configs])], lists:zip([Topic || {_, Topic} <- lists:sort([{I, Topic} || {[_, _, "subscription", I, "topic"], Topic} <- Configs])],
@ -1639,11 +1645,10 @@ end}.
maps:to_list( maps:to_list(
lists:foldl( lists:foldl(
fun({["bridge", Name, Opt], Val}, Acc) -> fun({["bridge", Name, Opt], Val}, Acc) ->
%% e.g #{aws => [{OptKey, OptVal}]}
Init = [{list_to_atom(Opt), Val},{subscriptions, Subscriptions(Name)}],
maps:update_with(list_to_atom(Name), maps:update_with(list_to_atom(Name),
fun(Opts) -> fun(Opts) -> Merge(list_to_atom(Opt), Val, Opts) end, Init, Acc);
Merge(list_to_atom(Opt), Val, Opts)
end, [{list_to_atom(Opt), Val},
{subscriptions, Subscriptions(Name)}], Acc);
(_, Acc) -> Acc (_, Acc) -> Acc
end, #{}, lists:usort(cuttlefish_variable:filter_by_prefix("bridge.", Conf)))) end, #{}, lists:usort(cuttlefish_variable:filter_by_prefix("bridge.", Conf))))

View File

@ -199,7 +199,8 @@ handle_info(start, State = #state{options = Options,
[emqx_client:subscribe(ClientPid, {Topic, Qos}) || {Topic, Qos} <- Subs], [emqx_client:subscribe(ClientPid, {Topic, Qos}) || {Topic, Qos} <- Subs],
[emqx_broker:subscribe(Topic) || Topic <- Forwards], [emqx_broker:subscribe(Topic) || Topic <- Forwards],
{noreply, State#state{client_pid = ClientPid, subscriptions = Subs, forwards = Forwards}}; {noreply, State#state{client_pid = ClientPid, subscriptions = Subs, forwards = Forwards}};
{error,_} -> {error, Reason} ->
logger:error("[Bridge] start failed! error: ~p", [Reason]),
erlang:send_after(ReconnectInterval, self(), start), erlang:send_after(ReconnectInterval, self(), start),
{noreply, State} {noreply, State}
end; end;
@ -285,6 +286,10 @@ options([{clean_start, CleanStart}| Options], Acc) ->
options([{address, Address}| Options], Acc) -> options([{address, Address}| Options], Acc) ->
{Host, Port} = address(Address), {Host, Port} = address(Address),
options(Options, [{host, Host}, {port, Port}|Acc]); options(Options, [{host, Host}, {port, Port}|Acc]);
options([{ssl, Ssl}| Options], Acc) ->
options(Options, [{ssl, Ssl}|Acc]);
options([{ssl_opts, SslOpts}| Options], Acc) ->
options(Options, [{ssl_opts, SslOpts}|Acc]);
options([_Option | Options], Acc) -> options([_Option | Options], Acc) ->
options(Options, Acc). options(Options, Acc).

View File

@ -538,14 +538,24 @@ init([{hosts, Hosts} | Opts], State) ->
init(Opts, State#state{hosts = Hosts1}); init(Opts, State#state{hosts = Hosts1});
init([{tcp_opts, TcpOpts} | Opts], State = #state{sock_opts = SockOpts}) -> init([{tcp_opts, TcpOpts} | Opts], State = #state{sock_opts = SockOpts}) ->
init(Opts, State#state{sock_opts = emqx_misc:merge_opts(SockOpts, TcpOpts)}); init(Opts, State#state{sock_opts = emqx_misc:merge_opts(SockOpts, TcpOpts)});
init([ssl | Opts], State = #state{sock_opts = SockOpts}) -> init([{ssl, EnableSsl} | Opts], State) ->
ok = ssl:start(), case lists:keytake(ssl_opts, 1, Opts) of
SockOpts1 = emqx_misc:merge_opts([{ssl_opts, []}], SockOpts), {value, SslOpts, WithOutSslOpts} ->
init(Opts, State#state{sock_opts = SockOpts1}); init([SslOpts, {ssl, EnableSsl}| WithOutSslOpts], State);
false ->
init([{ssl_opts, []}, {ssl, EnableSsl}| Opts], State)
end;
init([{ssl_opts, SslOpts} | Opts], State = #state{sock_opts = SockOpts}) -> init([{ssl_opts, SslOpts} | Opts], State = #state{sock_opts = SockOpts}) ->
case lists:keytake(ssl, 1, Opts) of
{value, {ssl, true}, WithOutEnableSsl} ->
ok = ssl:start(), ok = ssl:start(),
SockOpts1 = emqx_misc:merge_opts(SockOpts, [{ssl_opts, SslOpts}]), SockOpts1 = emqx_misc:merge_opts(SockOpts, [{ssl_opts, SslOpts}]),
init(Opts, State#state{sock_opts = SockOpts1}); init(WithOutEnableSsl, State#state{sock_opts = SockOpts1});
{value, {ssl, false}, WithOutEnableSsl} ->
init(WithOutEnableSsl, State);
false ->
init(Opts, State)
end;
init([{client_id, ClientId} | Opts], State) -> init([{client_id, ClientId} | Opts], State) ->
init(Opts, State#state{client_id = iolist_to_binary(ClientId)}); init(Opts, State#state{client_id = iolist_to_binary(ClientId)});
init([{clean_start, CleanStart} | Opts], State) when is_boolean(CleanStart) -> init([{clean_start, CleanStart} | Opts], State) when is_boolean(CleanStart) ->

View File

@ -49,7 +49,10 @@ connect(Host, Port, SockOpts, Timeout) ->
end. end.
ssl_upgrade(Sock, SslOpts, Timeout) -> ssl_upgrade(Sock, SslOpts, Timeout) ->
case ssl:connect(Sock, SslOpts, Timeout) of TlsVersions = proplists:get_value(versions, SslOpts, []),
Ciphers = proplists:get_value(ciphers, SslOpts, default_ciphers(TlsVersions)),
SslOpts2 = emqx_misc:merge_opts(SslOpts, [{ciphers, Ciphers}]),
case ssl:connect(Sock, SslOpts2, Timeout) of
{ok, SslSock} -> {ok, SslSock} ->
ok = ssl:controlling_process(SslSock, self()), ok = ssl:controlling_process(SslSock, self()),
{ok, #ssl_socket{tcp = Sock, ssl = SslSock}}; {ok, #ssl_socket{tcp = Sock, ssl = SslSock}};
@ -91,3 +94,8 @@ sockname(Sock) when is_port(Sock) ->
sockname(#ssl_socket{ssl = SslSock}) -> sockname(#ssl_socket{ssl = SslSock}) ->
ssl:sockname(SslSock). ssl:sockname(SslSock).
default_ciphers(TlsVersions) ->
lists:foldl(
fun(TlsVer, Ciphers) ->
Ciphers ++ ssl:cipher_suites(all, TlsVer)
end, [], TlsVersions).