Skip to content

Commit

Permalink
Merge pull request #11989 from rabbitmq/mk-encrypted-values-in-rabbit…
Browse files Browse the repository at this point in the history
…mq-conf

Make it possible to specify encrypted values in rabbitmq conf
  • Loading branch information
michaelklishin committed Aug 13, 2024
2 parents 267d7b8 + 8b90d4a commit dad09e6
Show file tree
Hide file tree
Showing 19 changed files with 584 additions and 28 deletions.
20 changes: 15 additions & 5 deletions deps/rabbit/priv/schema/rabbit.schema
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,12 @@ end}.
[{datatype, {enum, [true, false]}}]}.

{mapping, "definitions.tls.password", "rabbit.definitions.ssl_options.password",
[{datatype, string}]}.
[{datatype, [tagged_binary, binary]}]}.

{translation, "rabbit.definitions.ssl_options.password",
fun(Conf) ->
rabbit_cuttlefish:optionally_tagged_string("definitions.tls.password", Conf)
end}.

{mapping, "definitions.tls.secure_renegotiate", "rabbit.definitions.ssl_options.secure_renegotiate",
[{datatype, {enum, [true, false]}}]}.
Expand Down Expand Up @@ -395,7 +400,12 @@ end}.
[{datatype, {enum, [true, false]}}]}.

{mapping, "ssl_options.password", "rabbit.ssl_options.password",
[{datatype, string}]}.
[{datatype, [tagged_binary, binary]}]}.

{translation, "rabbit.ssl_options.password",
fun(Conf) ->
rabbit_cuttlefish:optionally_tagged_binary("ssl_options.password", Conf)
end}.

{mapping, "ssl_options.psk_identity", "rabbit.ssl_options.psk_identity",
[{datatype, string}]}.
Expand Down Expand Up @@ -656,12 +666,12 @@ fun(Conf) ->
end}.

{mapping, "default_pass", "rabbit.default_pass", [
{datatype, string}
{datatype, [tagged_binary, binary]}
]}.

{translation, "rabbit.default_pass",
fun(Conf) ->
list_to_binary(cuttlefish:conf_get("default_pass", Conf))
rabbit_cuttlefish:optionally_tagged_binary("default_pass", Conf)
end}.

{mapping, "default_permissions.configure", "rabbit.default_permissions", [
Expand Down Expand Up @@ -696,7 +706,7 @@ end}.
]}.

{mapping, "default_users.$name.password", "rabbit.default_users", [
{datatype, string}
{datatype, [tagged_binary, binary]}
]}.

{mapping, "default_users.$name.configure", "rabbit.default_users", [
Expand Down
27 changes: 26 additions & 1 deletion deps/rabbit/src/rabbit_cuttlefish.erl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@

-export([
aggregate_props/2,
aggregate_props/3
aggregate_props/3,

optionally_tagged_binary/2,
optionally_tagged_string/2
]).

-type keyed_props() :: [{binary(), [{binary(), any()}]}].
Expand Down Expand Up @@ -41,3 +44,25 @@ aggregate_props(Conf, Prefix, KeyFun) ->
FlatList
)
).

optionally_tagged_binary(Key, Conf) ->
case cuttlefish:conf_get(Key, Conf) of
undefined -> cuttlefish:unset();
{encrypted, Bin} when is_binary(Bin) -> {encrypted, Bin};
{_, Bin} when is_binary(Bin) -> {encrypted, Bin};
{encrypted, Str} when is_list(Str) -> {encrypted, list_to_binary(Str)};
{_, Str} when is_list(Str) -> {encrypted, list_to_binary(Str)};
Bin when is_binary(Bin) -> Bin;
Str when is_list(Str) -> list_to_binary(Str)
end.

optionally_tagged_string(Key, Conf) ->
case cuttlefish:conf_get(Key, Conf) of
undefined -> cuttlefish:unset();
{encrypted, Str} when is_list(Str) -> {encrypted, Str};
{_, Str} when is_list(Str) -> {encrypted, Str};
{encrypted, Bin} when is_binary(Bin) -> {encrypted, binary_to_list(Bin)};
{_, Bin} when is_binary(Bin) -> {encrypted, binary_to_list(Bin)};
Str when is_list(Str) -> Str;
Bin when is_binary(Bin) -> binary_to_list(Bin)
end.
4 changes: 2 additions & 2 deletions deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ ssl_options.fail_if_no_peer_cert = true",
[{rabbit, [{default_users, [
{<<"a">>, [{<<"vhost_pattern">>, "banana"},
{<<"tags">>, [administrator, operator]},
{<<"password">>, "SECRET"},
{<<"password">>, <<"SECRET">>},
{<<"read">>, ".*"}]}]}]}],
[]},

Expand Down Expand Up @@ -510,7 +510,7 @@ tcp_listen_options.exit_on_close = false",
[{cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"},
{certfile,"test/config_schema_SUITE_data/certs/cert.pem"},
{keyfile,"test/config_schema_SUITE_data/certs/key.pem"},
{password,"t0p$3kRe7"}]}]}],
{password,<<"t0p$3kRe7">>}]}]}],
[]},
{ssl_options_tls_ver_old,
"listeners.ssl.1 = 5671
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ end}.
[{datatype, {enum, [true, false]}}]}.

{mapping, "auth_http.ssl_options.password", "rabbitmq_auth_backend_http.ssl_options.password",
[{datatype, string}]}.
[{datatype, [tagged_binary, binary]}]}.

{mapping, "auth_http.ssl_options.psk_identity", "rabbitmq_auth_backend_http.ssl_options.psk_identity",
[{datatype, string}]}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
[{cacertfile,"test/config_schema_SUITE_data/certs/invalid_cacert.pem"},
{certfile,"test/config_schema_SUITE_data/certs/invalid_cert.pem"},
{keyfile,"test/config_schema_SUITE_data/certs/invalid_key.pem"},
{password,"t0p$3kRe7"}]}]}],
{password,<<"t0p$3kRe7">>}]}]}],
[]},
{ssl_options_tls_versions,
"auth_http.ssl_options.cacertfile = test/config_schema_SUITE_data/certs/invalid_cacert.pem
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DecodeCommand do
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error,
"Failed to decrypt the value. Things to check: is the passphrase correct? Are the cipher and hash algorithms the same as those used for encryption?"}
end
Expand All @@ -109,6 +110,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DecodeCommand do
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error,
"Failed to decrypt the value. Things to check: is the passphrase correct? Are the cipher and hash algorithms the same as those used for encryption?"}
end
Expand All @@ -117,15 +119,15 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DecodeCommand do
def formatter(), do: RabbitMQ.CLI.Formatters.Erlang

def banner(_, _) do
"Decrypting value..."
"Decrypting an advanced.config (Erlang term) value..."
end

def usage,
do: "decode value passphrase [--cipher <cipher>] [--hash <hash>] [--iterations <iterations>]"

def usage_additional() do
[
["<value>", "config value to decode"],
["<value>", "advanced.config (Erlang term) value to decode"],
["<passphrase>", "passphrase to use with the config value encryption key"],
["--cipher <cipher>", "cipher suite to use"],
["--hash <hash>", "hashing function to use"],
Expand All @@ -141,7 +143,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DecodeCommand do

def help_section(), do: :configuration

def description(), do: "Decrypts an encrypted configuration value"
def description(), do: "Decrypts an encrypted advanced.config value"

#
# Implementation
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
## This Source Code Form is subject to the terms of the Mozilla Public
## License, v. 2.0. If a copy of the MPL was not distributed with this
## file, You can obtain one at https://mozilla.org/MPL/2.0/.
##
## Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved.

alias RabbitMQ.CLI.Core.Helpers

defmodule RabbitMQ.CLI.Ctl.Commands.DecryptConfValueCommand do
alias RabbitMQ.CLI.Core.{DocGuide, Input}

@behaviour RabbitMQ.CLI.CommandBehaviour
use RabbitMQ.CLI.DefaultOutput

def switches() do
[
cipher: :string,
hash: :string,
iterations: :integer
]
end

@atomized_keys [:cipher, :hash]
@prefix "encrypted:"

def distribution(_), do: :none

def merge_defaults(args, opts) do
with_defaults =
Map.merge(
%{
cipher: :rabbit_pbe.default_cipher(),
hash: :rabbit_pbe.default_hash(),
iterations: :rabbit_pbe.default_iterations()
},
opts
)

{args, Helpers.atomize_values(with_defaults, @atomized_keys)}
end

def validate(args, _) when length(args) < 1 do
{:validation_failure, {:not_enough_args, "Please provide a value to decode and a passphrase"}}
end

def validate(args, _) when length(args) > 2 do
{:validation_failure, :too_many_args}
end

def validate(_args, opts) do
case {supports_cipher(opts.cipher), supports_hash(opts.hash), opts.iterations > 0} do
{false, _, _} ->
{:validation_failure, {:bad_argument, "The requested cipher is not supported"}}

{_, false, _} ->
{:validation_failure, {:bad_argument, "The requested hash is not supported"}}

{_, _, false} ->
{:validation_failure,
{:bad_argument,
"The requested number of iterations is incorrect (must be a positive integer)"}}

{true, true, true} ->
:ok
end
end

def run([value], %{cipher: cipher, hash: hash, iterations: iterations} = opts) do
case Input.consume_single_line_string_with_prompt("Passphrase: ", opts) do
:eof ->
{:error, :not_enough_args}

passphrase ->
try do
term_value = Helpers.evaluate_input_as_term(value)

term_to_decrypt =
case term_value do
prefixed_val when is_bitstring(prefixed_val) or is_list(prefixed_val) ->
tag_input_value_with_encrypted(prefixed_val)

{:encrypted, _} = encrypted ->
encrypted

_ ->
{:encrypted, term_value}
end

result = :rabbit_pbe.decrypt_term(cipher, hash, iterations, passphrase, term_to_decrypt)
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error,
"Failed to decrypt the value. Things to check: is the passphrase correct? Are the cipher and hash algorithms the same as those used for encryption?"}
end
end
end

def run([value, passphrase], %{cipher: cipher, hash: hash, iterations: iterations}) do
try do
term_value = Helpers.evaluate_input_as_term(value)

term_to_decrypt =
case term_value do
prefixed_val when is_bitstring(prefixed_val) or is_list(prefixed_val) ->
tag_input_value_with_encrypted(prefixed_val)

{:encrypted, _} = encrypted ->
encrypted

_ ->
{:encrypted, term_value}
end

result = :rabbit_pbe.decrypt_term(cipher, hash, iterations, passphrase, term_to_decrypt)
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error,
"Failed to decrypt the value. Things to check: is the passphrase correct? Are the cipher and hash algorithms the same as those used for encryption?"}
end
end

def formatter(), do: RabbitMQ.CLI.Formatters.Erlang

def banner(_, _) do
"Decrypting a rabbitmq.conf string value..."
end

def usage,
do: "decrypt_conf_value value passphrase [--cipher <cipher>] [--hash <hash>] [--iterations <iterations>]"

def usage_additional() do
[
["<value>", "a double-quoted rabbitmq.conf string value to decode"],
["<passphrase>", "passphrase to use with the config value encryption key"],
["--cipher <cipher>", "cipher suite to use"],
["--hash <hash>", "hashing function to use"],
["--iterations <iterations>", "number of iteration to apply"]
]
end

def usage_doc_guides() do
[
DocGuide.configuration()
]
end

def help_section(), do: :configuration

def description(), do: "Decrypts an encrypted configuration value"

#
# Implementation
#

defp supports_cipher(cipher), do: Enum.member?(:rabbit_pbe.supported_ciphers(), cipher)

defp supports_hash(hash), do: Enum.member?(:rabbit_pbe.supported_hashes(), hash)

defp tag_input_value_with_encrypted(value) when is_bitstring(value) or is_list(value) do
bin_val = :rabbit_data_coercion.to_binary(value)
untagged_val = String.replace_prefix(bin_val, @prefix, "")

{:encrypted, untagged_val}
end
defp tag_input_value_with_encrypted(value) do
{:encrypted, value}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error, "Error during cipher operation"}
end
end
Expand All @@ -99,6 +100,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error, "Error during cipher operation"}
end
end
Expand All @@ -115,22 +117,23 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do
{:ok, result}
catch
_, _ ->
IO.inspect(__STACKTRACE__)
{:error, "Error during cipher operation"}
end
end

def formatter(), do: RabbitMQ.CLI.Formatters.Erlang

def banner(_, _) do
"Encrypting value ..."
"Encrypting value to be used in advanced.config..."
end

def usage,
do: "encode value passphrase [--cipher <cipher>] [--hash <hash>] [--iterations <iterations>]"

def usage_additional() do
[
["<value>", "config value to encode"],
["<value>", "value to encode, to be used in advanced.config"],
["<passphrase>", "passphrase to use with the config value encryption key"],
["--cipher <cipher>", "cipher suite to use"],
["--hash <hash>", "hashing function to use"],
Expand All @@ -146,7 +149,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do

def help_section(), do: :configuration

def description(), do: "Encrypts a sensitive configuration value"
def description(), do: "Encrypts a sensitive configuration value to be used in the advanced.config file"

#
# Implementation
Expand Down
Loading

0 comments on commit dad09e6

Please sign in to comment.