Skip to content

Commit ae67c3e

Browse files
authored
Merge pull request #994 from rabbitmq/rabbitmq-server-979
Add optional password based encryption of configuration values
2 parents 157cdaa + c5991c6 commit ae67c3e

File tree

9 files changed

+736
-4
lines changed

9 files changed

+736
-4
lines changed

include/rabbit_cli.hrl

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@
3131
-define(ONLINE_OPT, "--online").
3232
-define(LOCAL_OPT, "--local").
3333

34+
-define(DECODE_OPT, "--decode").
35+
-define(CIPHER_OPT, "--cipher").
36+
-define(HASH_OPT, "--hash").
37+
-define(ITERATIONS_OPT, "--iterations").
38+
-define(LIST_CIPHERS_OPT, "--list-ciphers").
39+
-define(LIST_HASHES_OPT, "--list-hashes").
3440

3541
-define(NODE_DEF(Node), {?NODE_OPT, {option, Node}}).
3642
-define(QUIET_DEF, {?QUIET_OPT, flag}).
@@ -48,6 +54,13 @@
4854
-define(OFFLINE_DEF, {?OFFLINE_OPT, flag}).
4955
-define(ONLINE_DEF, {?ONLINE_OPT, flag}).
5056
-define(LOCAL_DEF, {?LOCAL_OPT, flag}).
57+
-define(DECODE_DEF, {?DECODE_OPT, flag}).
58+
-define(CIPHER_DEF, {?CIPHER_OPT, {option, atom_to_list(rabbit_pbe:default_cipher())}}).
59+
-define(HASH_DEF, {?HASH_OPT, {option, atom_to_list(rabbit_pbe:default_hash())}}).
60+
-define(ITERATIONS_DEF, {?ITERATIONS_OPT, {option, integer_to_list(rabbit_pbe:default_iterations())}}).
61+
-define(LIST_CIPHERS_DEF, {?LIST_CIPHERS_OPT, flag}).
62+
-define(LIST_HASHES_DEF, {?LIST_HASHES_OPT, flag}).
63+
5164

5265
%% Subset of standartized exit codes from sysexits.h, see
5366
%% https://github.com/rabbitmq/rabbitmq-server/issues/396 for discussion.

src/rabbit.app.src

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,12 @@
100100
%% see rabbitmq-server#248
101101
%% and rabbitmq-server#667
102102
{channel_operation_timeout, 15000},
103+
{decoder_config, [
104+
{cipher, aes_cbc256},
105+
{hash, sha512},
106+
{iterations, 1000},
107+
{passphrase, undefined}
108+
]},
103109
%% rabbitmq-server-973
104110
{lazy_queue_explicit_gc_run_operation_threshold, 250}
105111
]}]}.

src/rabbit.erl

Lines changed: 105 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
start_fhc/0]).
2525
-export([start/2, stop/1, prep_stop/1]).
2626
-export([start_apps/1, stop_apps/1]).
27-
-export([log_location/1, config_files/0]). %% for testing and mgmt-agent
27+
-export([log_location/1, config_files/0, decrypt_config/2]). %% for testing and mgmt-agent
2828

2929
%%---------------------------------------------------------------------------
3030
%% Boot steps.
@@ -442,6 +442,38 @@ stop_and_halt() ->
442442

443443
start_apps(Apps) ->
444444
app_utils:load_applications(Apps),
445+
446+
DecoderConfig = case application:get_env(rabbit, decoder_config) of
447+
undefined ->
448+
[];
449+
{ok, Val} ->
450+
Val
451+
end,
452+
PassPhrase = case proplists:get_value(passphrase, DecoderConfig) of
453+
prompt ->
454+
IoDevice = get_input_iodevice(),
455+
io:setopts(IoDevice, [{echo, false}]),
456+
PP = lists:droplast(io:get_line(IoDevice,
457+
"\nPlease enter the passphrase to unlock encrypted "
458+
"configuration entries.\n\nPassphrase: ")),
459+
io:setopts(IoDevice, [{echo, true}]),
460+
io:format(IoDevice, "~n", []),
461+
PP;
462+
{file, Filename} ->
463+
{ok, File} = file:read_file(Filename),
464+
[PP|_] = binary:split(File, [<<"\r\n">>, <<"\n">>]),
465+
PP;
466+
PP ->
467+
PP
468+
end,
469+
Algo = {
470+
proplists:get_value(cipher, DecoderConfig, rabbit_pbe:default_cipher()),
471+
proplists:get_value(hash, DecoderConfig, rabbit_pbe:default_hash()),
472+
proplists:get_value(iterations, DecoderConfig, rabbit_pbe:default_iterations()),
473+
PassPhrase
474+
},
475+
decrypt_config(Apps, Algo),
476+
445477
OrderedApps = app_utils:app_dependency_order(Apps, false),
446478
case lists:member(rabbit, Apps) of
447479
false -> rabbit_boot_steps:run_boot_steps(Apps); %% plugin activation
@@ -450,6 +482,78 @@ start_apps(Apps) ->
450482
ok = app_utils:start_applications(OrderedApps,
451483
handle_app_error(could_not_start)).
452484

485+
%% This function retrieves the correct IoDevice for requesting
486+
%% input. The problem with using the default IoDevice is that
487+
%% the Erlang shell prevents us from getting the input.
488+
%%
489+
%% Instead we therefore look for the io process used by the
490+
%% shell and if it can't be found (because the shell is not
491+
%% started e.g with -noshell) we use the 'user' process.
492+
%%
493+
%% This function will not work when either -oldshell or -noinput
494+
%% options are passed to erl.
495+
get_input_iodevice() ->
496+
case whereis(user) of
497+
undefined -> user;
498+
User ->
499+
case group:interfaces(User) of
500+
[] ->
501+
user;
502+
[{user_drv, Drv}] ->
503+
case user_drv:interfaces(Drv) of
504+
[] ->
505+
user;
506+
[{current_group, IoDevice}] ->
507+
IoDevice
508+
end
509+
end
510+
end.
511+
512+
decrypt_config([], _) ->
513+
ok;
514+
decrypt_config([App|Apps], Algo) ->
515+
decrypt_app(App, application:get_all_env(App), Algo),
516+
decrypt_config(Apps, Algo).
517+
518+
decrypt_app(_, [], _) ->
519+
ok;
520+
decrypt_app(App, [{Key, Value}|Tail], Algo) ->
521+
try begin
522+
case decrypt(Value, Algo) of
523+
Value ->
524+
ok;
525+
NewValue ->
526+
application:set_env(App, Key, NewValue)
527+
end
528+
end
529+
catch
530+
exit:{bad_configuration, decoder_config} ->
531+
exit({bad_configuration, decoder_config});
532+
_:Msg ->
533+
rabbit_log:info("Error while decrypting key '~p'. Please check encrypted value, passphrase, and encryption configuration~n", [Key]),
534+
exit({decryption_error, {key, Key}, Msg})
535+
end,
536+
decrypt_app(App, Tail, Algo).
537+
538+
decrypt({encrypted, _}, {_, _, _, undefined}) ->
539+
exit({bad_configuration, decoder_config});
540+
decrypt({encrypted, EncValue}, {Cipher, Hash, Iterations, Password}) ->
541+
rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, Password, EncValue);
542+
decrypt(List, Algo) when is_list(List) ->
543+
decrypt_list(List, Algo, []);
544+
decrypt(Value, _) ->
545+
Value.
546+
547+
%% We make no distinction between strings and other lists.
548+
%% When we receive a string, we loop through each element
549+
%% and ultimately return the string unmodified, as intended.
550+
decrypt_list([], _, Acc) ->
551+
lists:reverse(Acc);
552+
decrypt_list([{Key, Value}|Tail], Algo, Acc) when Key =/= encrypted ->
553+
decrypt_list(Tail, Algo, [{Key, decrypt(Value, Algo)}|Acc]);
554+
decrypt_list([Value|Tail], Algo, Acc) ->
555+
decrypt_list(Tail, Algo, [decrypt(Value, Algo)|Acc]).
556+
453557
stop_apps(Apps) ->
454558
ok = app_utils:stop_applications(
455559
Apps, handle_app_error(error_during_shutdown)),

src/rabbit_control_main.erl

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@
9292
{trace_off, [?VHOST_DEF]},
9393
set_vm_memory_high_watermark,
9494
set_disk_free_limit,
95-
help
95+
help,
96+
{encode, [?DECODE_DEF, ?CIPHER_DEF, ?HASH_DEF, ?ITERATIONS_DEF, ?LIST_CIPHERS_DEF, ?LIST_HASHES_DEF]}
9697
]).
9798

9899
-define(GLOBAL_QUERIES,
@@ -114,7 +115,7 @@
114115
[stop, stop_app, start_app, wait, reset, force_reset, rotate_logs,
115116
join_cluster, change_cluster_node_type, update_cluster_nodes,
116117
forget_cluster_node, rename_cluster_node, cluster_status, status,
117-
environment, eval, force_boot, help, hipe_compile]).
118+
environment, eval, force_boot, help, hipe_compile, encode]).
118119

119120
%% [Command | {Command, DefaultTimeoutInMilliSeconds}]
120121
-define(COMMANDS_WITH_TIMEOUT,
@@ -579,6 +580,17 @@ action(eval, Node, [Expr], _Opts, _Inform) ->
579580
action(help, _Node, _Args, _Opts, _Inform) ->
580581
io:format("~s", [rabbit_ctl_usage:usage()]);
581582

583+
action(encode, _Node, Args, Opts, _Inform) ->
584+
ListCiphers = lists:member({?LIST_CIPHERS_OPT, true}, Opts),
585+
ListHashes = lists:member({?LIST_HASHES_OPT, true}, Opts),
586+
Decode = lists:member({?DECODE_OPT, true}, Opts),
587+
Cipher = list_to_atom(proplists:get_value(?CIPHER_OPT, Opts)),
588+
Hash = list_to_atom(proplists:get_value(?HASH_OPT, Opts)),
589+
Iterations = list_to_integer(proplists:get_value(?ITERATIONS_OPT, Opts)),
590+
591+
{_, Msg} = rabbit_control_pbe:encode(ListCiphers, ListHashes, Decode, Cipher, Hash, Iterations, Args),
592+
io:format(Msg ++ "~n");
593+
582594
action(Command, Node, Args, Opts, Inform) ->
583595
%% For backward compatibility, run commands accepting a timeout with
584596
%% the default timeout.

src/rabbit_control_pbe.erl

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
% The contents of this file are subject to the Mozilla Public License
2+
%% Version 1.1 (the "License"); you may not use this file except in
3+
%% compliance with the License. You may obtain a copy of the License
4+
%% at http://www.mozilla.org/MPL/
5+
%%
6+
%% Software distributed under the License is distributed on an "AS IS"
7+
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
8+
%% the License for the specific language governing rights and
9+
%% limitations under the License.
10+
%%
11+
%% The Original Code is RabbitMQ.
12+
%%
13+
%% The Initial Developer of the Original Code is GoPivotal, Inc.
14+
%% Copyright (c) 2007-2016 Pivotal Software, Inc. All rights reserved.
15+
%%
16+
17+
-module(rabbit_control_pbe).
18+
19+
-export([encode/7]).
20+
21+
% for testing purposes
22+
-export([evaluate_input_as_term/1]).
23+
24+
encode(ListCiphers, _ListHashes, _Decode, _Cipher, _Hash, _Iterations, _Args) when ListCiphers ->
25+
{ok, io_lib:format("~p", [rabbit_pbe:supported_ciphers()])};
26+
27+
encode(_ListCiphers, ListHashes, _Decode, _Cipher, _Hash, _Iterations, _Args) when ListHashes ->
28+
{ok, io_lib:format("~p", [rabbit_pbe:supported_hashes()])};
29+
30+
encode(_ListCiphers, _ListHashes, Decode, Cipher, Hash, Iterations, Args) ->
31+
CipherExists = lists:member(Cipher, rabbit_pbe:supported_ciphers()),
32+
HashExists = lists:member(Hash, rabbit_pbe:supported_hashes()),
33+
encode_encrypt_decrypt(CipherExists, HashExists, Decode, Cipher, Hash, Iterations, Args).
34+
35+
encode_encrypt_decrypt(CipherExists, _HashExists, _Decode, _Cipher, _Hash, _Iterations, _Args) when CipherExists =:= false ->
36+
{error, io_lib:format("The requested cipher is not supported", [])};
37+
38+
encode_encrypt_decrypt(_CipherExists, HashExists, _Decode, _Cipher, _Hash, _Iterations, _Args) when HashExists =:= false ->
39+
{error, io_lib:format("The requested hash is not supported", [])};
40+
41+
encode_encrypt_decrypt(_CipherExists, _HashExists, _Decode, _Cipher, _Hash, Iterations, _Args) when Iterations =< 0 ->
42+
{error, io_lib:format("The requested number of iterations is incorrect", [])};
43+
44+
encode_encrypt_decrypt(_CipherExists, _HashExists, Decode, Cipher, Hash, Iterations, Args) when length(Args) == 2, Decode =:= false ->
45+
[Value, PassPhrase] = Args,
46+
try begin
47+
TermValue = evaluate_input_as_term(Value),
48+
Result = rabbit_pbe:encrypt_term(Cipher, Hash, Iterations, list_to_binary(PassPhrase), TermValue),
49+
{ok, io_lib:format("~p", [{encrypted, Result}])}
50+
end
51+
catch
52+
_:Msg -> {error, io_lib:format("Error during cipher operation: ~p", [Msg])}
53+
end;
54+
55+
encode_encrypt_decrypt(_CipherExists, _HashExists, Decode, Cipher, Hash, Iterations, Args) when length(Args) == 2, Decode ->
56+
[Value, PassPhrase] = Args,
57+
try begin
58+
TermValue = evaluate_input_as_term(Value),
59+
TermToDecrypt = case TermValue of
60+
{encrypted, EncryptedTerm} ->
61+
EncryptedTerm;
62+
_ ->
63+
TermValue
64+
end,
65+
Result = rabbit_pbe:decrypt_term(Cipher, Hash, Iterations, list_to_binary(PassPhrase), TermToDecrypt),
66+
{ok, io_lib:format("~p", [Result])}
67+
end
68+
catch
69+
_:Msg -> {error, io_lib:format("Error during cipher operation: ~p", [Msg])}
70+
end;
71+
72+
encode_encrypt_decrypt(_CipherExists, _HashExists, _Decode, _Cipher, _Hash, _Iterations, _Args) ->
73+
{error, io_lib:format("Please provide a value to encode/decode and a passphrase", [])}.
74+
75+
evaluate_input_as_term(Input) ->
76+
{ok,Tokens,_EndLine} = erl_scan:string(Input ++ "."),
77+
{ok,AbsForm} = erl_parse:parse_exprs(Tokens),
78+
{value,TermValue,_Bs} = erl_eval:exprs(AbsForm, erl_eval:new_bindings()),
79+
TermValue.

0 commit comments

Comments
 (0)