Skip to content

Commit

Permalink
Add helper to create TLS replication configuration
Browse files Browse the repository at this point in the history
From TLS dist configuration.

References #16
  • Loading branch information
acogoluegnes committed Oct 22, 2021
1 parent f116bc0 commit 12f96fe
Show file tree
Hide file tree
Showing 5 changed files with 368 additions and 13 deletions.
191 changes: 179 additions & 12 deletions src/osiris_util.erl
Expand Up @@ -7,11 +7,22 @@

-module(osiris_util).

-include("osiris.hrl").

-export([validate_base64uri/1,
to_base64uri/1,
id/1,
lists_find/2,
hostname_from_node/0]).
hostname_from_node/0,
get_replication_configuration_from_tls_dist/0,
get_replication_configuration_from_tls_dist/1]).

-ifdef(TEST).

-export([inet_tls_enabled/1,
replication_over_tls_configuration/2]).

-endif.

-define(BASE64_URI_CHARS,
"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz01"
Expand Down Expand Up @@ -58,15 +69,171 @@ lists_find(Pred, [Item | Rem]) ->
lists_find(Pred, Rem)
end.


hostname_from_node() ->
case re:split(
atom_to_list(node()), "@",
[{return, list}, {parts, 2}])
of
[_, Hostname] ->
Hostname;
[_] ->
{ok, H} = inet:gethostname(),
rabbit_data_coercion:to_list(H)
end.
case re:split(atom_to_list(node()), "@", [{return, list}, {parts, 2}])
of
[_, Hostname] ->
Hostname;
[_] ->
{ok, H} = inet:gethostname(),
rabbit_data_coercion:to_list(H)
end.

get_replication_configuration_from_tls_dist() ->
get_replication_configuration_from_tls_dist(fun (debug, Fmt, Args) ->
?DEBUG(Fmt, Args);
(warn, Fmt, Args) ->
?WARN(Fmt, Args);
(warning, Fmt, Args) ->
?WARN(Fmt, Args);
(_, Fmt, Args) ->
?INFO(Fmt, Args)
end).

get_replication_configuration_from_tls_dist(LogFun) ->
InitArguments = init:get_arguments(),
case inet_tls_enabled(InitArguments) of
true ->
LogFun(debug,
"Inter-node TLS enabled, "
++ "configuring stream replication over TLS",
[]),
replication_over_tls_configuration(InitArguments, LogFun);
false ->
LogFun(debug, "Inter-node TLS not enabled", []),
[]
end.

replication_over_tls_configuration(InitArgs, LogFun) ->
case proplists:lookup(ssl_dist_optfile, InitArgs) of
none ->
LogFun(debug,
"Using ssl_dist_opt to configure "
++ "stream replication over TLS",
[]),
SslDistOpt = proplists:lookup_all(ssl_dist_opt, InitArgs),
[{replication_transport, ssl},
{replication_server_ssl_options,
build_replication_over_tls_options("server_", SslDistOpt, [])},
{replication_client_ssl_options,
build_replication_over_tls_options("client_", SslDistOpt, [])}];
{ssl_dist_optfile, [OptFile]} ->
LogFun(debug,
"Using ssl_dist_optfile to configure "
++ "stream replication over TLS",
[]),
case file:consult(OptFile) of
{ok, [TlsDist]} ->
SslServerOptions = proplists:get_value(server, TlsDist, []),
SslClientOptions = proplists:get_value(client, TlsDist, []),
[{replication_transport, ssl},
{replication_server_ssl_options, SslServerOptions},
{replication_client_ssl_options, SslClientOptions}];
{error, Error} ->
LogFun(warn,
"Error while reading TLS "
++ "distributon option file ~s: ~p",
[OptFile, Error]),
LogFun(warn,
"Stream replication over TLS will NOT be enabled",
[]),
[];
R ->
LogFun(warn,
"Unexpected result while reading TLS distributon "
"option file ~s: ~p",
[OptFile, R]),
LogFun(warn,
"Stream replication over TLS will NOT be enabled",
[]),
[]
end
end.

build_replication_over_tls_options(_Prefix, [], Acc) ->
Acc;
build_replication_over_tls_options("server_" = Prefix,
[{ssl_dist_opt, ["server_" ++ Key, Value]}
| Tail],
Acc) ->
Option = list_to_atom(Key),
build_replication_over_tls_options(Prefix, Tail,
Acc
++ [extract_replication_over_tls_option(Option,
Value)]);
build_replication_over_tls_options("client_" = Prefix,
[{ssl_dist_opt, ["client_" ++ Key, Value]}
| Tail],
Acc) ->
Option = list_to_atom(Key),
build_replication_over_tls_options(Prefix, Tail,
Acc
++ [extract_replication_over_tls_option(Option,
Value)]);
build_replication_over_tls_options(Prefix,
[{ssl_dist_opt, [Key1, Value1, Key2, Value2]}
| Tail],
Acc) ->
%% For -ssl_dist_opt server_secure_renegotiate true client_secure_renegotiate true
build_replication_over_tls_options(Prefix,
[{ssl_dist_opt, [Key1, Value1]},
{ssl_dist_opt, [Key2, Value2]}]
++ Tail,
Acc);
build_replication_over_tls_options(Prefix,
[{ssl_dist_opt, [_Key, _Value]} | Tail],
Acc) ->
build_replication_over_tls_options(Prefix, Tail, Acc).

extract_replication_over_tls_option(certfile, V) ->
{certfile, V};
extract_replication_over_tls_option(keyfile, V) ->
{keyfile, V};
extract_replication_over_tls_option(password, V) ->
{password, V};
extract_replication_over_tls_option(cacertfile, V) ->
{cacertfile, V};
extract_replication_over_tls_option(verify, V) ->
{verify, list_to_atom(V)};
extract_replication_over_tls_option(verify_fun, V) ->
%% Write as {Module, Function, InitialUserState}
{verify_fun, eval_term(V)};
extract_replication_over_tls_option(crl_check, V) ->
{crl_check, list_to_atom(V)};
extract_replication_over_tls_option(crl_cache, V) ->
%% Write as Erlang term
{crl_cache, eval_term(V)};
extract_replication_over_tls_option(reuse_sessions, V) ->
%% boolean() | save
{reuse_sessions, eval_term(V)};
extract_replication_over_tls_option(secure_renegotiate, V) ->
{secure_renegotiate, list_to_atom(V)};
extract_replication_over_tls_option(depth, V) ->
{depth, list_to_integer(V)};
extract_replication_over_tls_option(hibernate_after, "undefined") ->
{hibernate_after, undefined};
extract_replication_over_tls_option(hibernate_after, V) ->
{hibernate_after, list_to_integer(V)};
extract_replication_over_tls_option(ciphers, V) ->
%% Use old string format
%% e.g. TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256
{ciphers, V};
extract_replication_over_tls_option(fail_if_no_peer_cert, V) ->
{fail_if_no_peer_cert, list_to_atom(V)};
extract_replication_over_tls_option(dhfile, V) ->
{dhfile, V}.

eval_term(V) ->
{ok, Tokens, _EndLine} = erl_scan:string(V ++ "."),
{ok, AbsForm} = erl_parse:parse_exprs(Tokens),
{value, Term, _Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
Term.

inet_tls_enabled([]) ->
false;
inet_tls_enabled([{proto_dist, ["inet_tls"]} | _]) ->
true;
inet_tls_enabled([_Opt | Tail]) ->
inet_tls_enabled(Tail);
inet_tls_enabled(_Opt) ->
false.
133 changes: 132 additions & 1 deletion test/osiris_util_SUITE.erl
Expand Up @@ -23,7 +23,10 @@ all() ->
[{group, tests}].

all_tests() ->
[to_base64uri_test].
[to_base64uri_test,
inet_tls_enabled,
replication_over_tls_configuration_with_optfile,
replication_over_tls_configuration_with_opt].

groups() ->
[{tests, [], all_tests()}].
Expand Down Expand Up @@ -55,3 +58,131 @@ to_base64uri_test(_Config) ->
?assertEqual("my__queue", osiris_util:to_base64uri("my%*queue")),
?assertEqual("my99___queue",
osiris_util:to_base64uri("my99_[]queue")).

inet_tls_enabled(_) ->
InitArgs = init:get_arguments(),
?assert(osiris_util:inet_tls_enabled(InitArgs
++ [{proto_dist, ["inet_tls"]}])),
?assertNot(osiris_util:inet_tls_enabled(InitArgs)),
ok.

replication_over_tls_configuration_with_optfile(Config) ->
ExpectedOkConfig =
[{replication_transport, ssl},
{replication_server_ssl_options,
[{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/server_certificate.pem"},
{keyfile, "/etc/rabbitmq/server_key.pem"},
{secure_renegotiate, true},
{verify, verify_peer},
{fail_if_no_peer_cert, true}]},
{replication_client_ssl_options,
[{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/client_certificate.pem"},
{keyfile, "/etc/rabbitmq/client_key.pem"},
{secure_renegotiate, true},
{verify, verify_peer},
{fail_if_no_peer_cert, true}]}],
[begin
InitArgs =
[{proto_dist, ["inet_tls"]},
{ssl_dist_optfile, [?config(data_dir, Config) ++ File]}],
?assertEqual(ExpectedOkConfig,
replication_over_tls_configuration(InitArgs))
end
|| File
<- ["inter_node_tls_server_client_ok.config",
"inter_node_tls_client_server_ok.config"]],

FileBroken =
?config(data_dir, Config) ++ "inter_node_tls_broken.config",
InitArgsBroken =
[{proto_dist, ["inet_tls"]}, {ssl_dist_optfile, [FileBroken]}],
?assertEqual([], replication_over_tls_configuration(InitArgsBroken)),

FileNotFound =
?config(data_dir, Config) ++ "inter_node_tls_not_found.config",
InitArgsNotFound =
[{proto_dist, ["inet_tls"]}, {ssl_dist_optfile, [FileNotFound]}],
?assertEqual([],
replication_over_tls_configuration(InitArgsNotFound)),

ok.

replication_over_tls_configuration_with_opt(_) ->
InitArgs =
[{proto_dist, ["inet_tls"]},
{ssl_dist_opt,
["server_cacertfile", "/etc/rabbitmq/ca_certificate.pem"]},
{ssl_dist_opt,
["server_certfile", "/etc/rabbitmq/server_certificate.pem"]},
{ssl_dist_opt, ["server_keyfile", "/etc/rabbitmq/server_key.pem"]},
{ssl_dist_opt, ["server_verify", "verify_peer"]},
{ssl_dist_opt, ["server_fail_if_no_peer_cert", "true"]},
{ssl_dist_opt,
["client_cacertfile", "/etc/rabbitmq/ca_certificate.pem"]},
{ssl_dist_opt,
["client_certfile", "/etc/rabbitmq/client_certificate.pem"]},
{ssl_dist_opt, ["client_keyfile", "/etc/rabbitmq/client_key.pem"]},
{ssl_dist_opt, ["client_verify", "verify_peer"]},
{ssl_dist_opt,
["server_secure_renegotiate",
"true",
"client_secure_renegotiate",
"true"]}],

?assertEqual([{replication_transport, ssl},
{replication_server_ssl_options,
[{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/server_certificate.pem"},
{keyfile, "/etc/rabbitmq/server_key.pem"},
{verify, verify_peer},
{fail_if_no_peer_cert, true},
{secure_renegotiate, true}]},
{replication_client_ssl_options,
[{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/client_certificate.pem"},
{keyfile, "/etc/rabbitmq/client_key.pem"},
{verify, verify_peer},
{secure_renegotiate, true}]}],
replication_over_tls_configuration(InitArgs)),

ExtraInitArgs =
[{proto_dist, ["inet_tls"]},
{ssl_dist_opt,
["server_verify_fun",
"{some_module,some_function,some_initial_state}"]},
{ssl_dist_opt, ["server_crl_check", "true"]},
{ssl_dist_opt,
["server_crl_cache", "{ssl_crl_cache, {internal, []}}"]},
{ssl_dist_opt, ["server_reuse_sessions", "save"]},
{ssl_dist_opt, ["server_depth", "1"]},
{ssl_dist_opt, ["server_hibernate_after", "10"]},
{ssl_dist_opt,
["server_ciphers", "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256"]},
{ssl_dist_opt, ["server_dhfile", "/some/file"]},
{ssl_dist_opt, ["server_password", "bunnies"]}],

?assertEqual([{replication_transport, ssl},
{replication_server_ssl_options,
[{verify_fun,
{some_module, some_function, some_initial_state}},
{crl_check, true},
{crl_cache, {ssl_crl_cache, {internal, []}}},
{reuse_sessions, save},
{depth, 1},
{hibernate_after, 10},
{ciphers, "TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256"},
{dhfile, "/some/file"},
{password, "bunnies"}]},
{replication_client_ssl_options, []}],
replication_over_tls_configuration(ExtraInitArgs)),

ok.

replication_over_tls_configuration(Args) ->
osiris_util:replication_over_tls_configuration(Args,
fun tls_replication_log/3).

tls_replication_log(_Level, Fmt, Args) ->
ct:log(Fmt, Args).
21 changes: 21 additions & 0 deletions test/osiris_util_SUITE_data/inter_node_tls_broken.config
@@ -0,0 +1,21 @@
[
{server, [
{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/server_certificate.pem"},
{keyfile, "/etc/rabbitmq/server_key.pem"},
{secure_renegotiate, true},
{verify, verify_peer},
{fail_if_no_peer_cert, true}
]}

BROKEN

{client, [
{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/client_certificate.pem"},
{keyfile, "/etc/rabbitmq/client_key.pem"},
{secure_renegotiate, true},
{verify, verify_peer},
{fail_if_no_peer_cert, true}
]}
].
18 changes: 18 additions & 0 deletions test/osiris_util_SUITE_data/inter_node_tls_client_server_ok.config
@@ -0,0 +1,18 @@
[
{client, [
{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/client_certificate.pem"},
{keyfile, "/etc/rabbitmq/client_key.pem"},
{secure_renegotiate, true},
{verify, verify_peer},
{fail_if_no_peer_cert, true}
]},
{server, [
{cacertfile, "/etc/rabbitmq/ca_certificate.pem"},
{certfile, "/etc/rabbitmq/server_certificate.pem"},
{keyfile, "/etc/rabbitmq/server_key.pem"},
{secure_renegotiate, true},
{verify, verify_peer},
{fail_if_no_peer_cert, true}
]}
].

0 comments on commit 12f96fe

Please sign in to comment.