From ef46119695aaa70ab238186a3b0c8828065333da Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Wed, 7 Aug 2024 20:11:39 -0400 Subject: [PATCH 1/6] Initial encrypted value support for rabbitmq.conf This makes possible to specify an encrypted value in rabbitmq.conf using a prefix. For example, to specify a default user password as an encrypted value: ``` ini default_user = bunnies-444 default_pass = encrypted:F/bjQkteQENB4rMUXFKdgsJEpYMXYLzBY/AmcYG83Tg8AOUwYP7Oa0Q33ooNEpK9 ``` ``` erl [ {rabbit, [ {config_entry_decoder, [ {passphrase, <<"bunnies">>} ]} ]} ]. ``` (cherry picked from commit 1c7e5904952732ad159dea93f36e0c7314e0e95e) --- deps/rabbit/priv/schema/rabbit.schema | 18 ++++++++++++++---- deps/rabbit/src/rabbit_cuttlefish.erl | 27 ++++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 5 deletions(-) diff --git a/deps/rabbit/priv/schema/rabbit.schema b/deps/rabbit/priv/schema/rabbit.schema index 07624a055f85..406ce3582abf 100644 --- a/deps/rabbit/priv/schema/rabbit.schema +++ b/deps/rabbit/priv/schema/rabbit.schema @@ -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]}}]}. @@ -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}]}. @@ -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", [ diff --git a/deps/rabbit/src/rabbit_cuttlefish.erl b/deps/rabbit/src/rabbit_cuttlefish.erl index 18dbc282d46f..f43b4a1f4745 100644 --- a/deps/rabbit/src/rabbit_cuttlefish.erl +++ b/deps/rabbit/src/rabbit_cuttlefish.erl @@ -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()}]}]. @@ -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. \ No newline at end of file From 53e8397b053bab6ddd0434aa09ce360bfe3cfaa7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 13 Aug 2024 12:29:28 -0400 Subject: [PATCH 2/6] Secret encoding: refine CLI tools 'ctl encode' is unfortunately name and targets advanced.config commands. This introduce a command that targets 'rabbitmq.conf' values and has a more specific name. Eventually 'ctl encode' will be aliased and deprecated, although we still do not have an aliasing mechanism and it won't be in scope for 4.0. (cherry picked from commit c2fdd73c4b7bd22b3803c6063aec4a6193c7bfc9) --- .../cli/ctl/commands/encode_command.ex | 4 +- .../commands/encrypt_conf_value_command.ex | 155 ++++++++++++++++++ .../cli/formatters/encrypted_conf_value.ex | 26 +++ 3 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex create mode 100644 deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/encrypted_conf_value.ex diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex index ae69e44b72d0..368bbd78c24b 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex @@ -122,7 +122,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do def formatter(), do: RabbitMQ.CLI.Formatters.Erlang def banner(_, _) do - "Encrypting value ..." + "Encrypting value to be used in advanced.config..." end def usage, @@ -146,7 +146,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 diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex new file mode 100644 index 000000000000..f330f7732736 --- /dev/null +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex @@ -0,0 +1,155 @@ +## 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. + +defmodule RabbitMQ.CLI.Ctl.Commands.EncryptConfValueCommand do + alias RabbitMQ.CLI.Core.{DocGuide, Helpers, Input} + + @behaviour RabbitMQ.CLI.CommandBehaviour + use RabbitMQ.CLI.DefaultOutput + + def switches() do + [ + cipher: :string, + hash: :string, + iterations: :integer + ] + end + + @atomized_keys [:cipher, :hash] + + 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) > 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"}} + + {true, true, true} -> + :ok + end + end + + def run([], %{cipher: cipher, hash: hash, iterations: iterations} = opts) do + case Input.consume_single_line_string_with_prompt("Value to encode: ", opts) do + :eof -> + {:error, :not_enough_args} + + value -> + 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) + + {:encrypted, result} = + :rabbit_pbe.encrypt_term(cipher, hash, iterations, passphrase, term_value) + + {:ok, result} + catch + _, _ -> + {:error, "Error during cipher operation"} + end + end + 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) + + {:encrypted, result} = + :rabbit_pbe.encrypt_term(cipher, hash, iterations, passphrase, term_value) + + {:ok, result} + catch + _, _ -> + {:error, "Error during cipher operation"} + end + end + end + + def run([value, passphrase], %{cipher: cipher, hash: hash, iterations: iterations}) do + try do + term_value = Helpers.evaluate_input_as_term(value) + + {:encrypted, result} = + :rabbit_pbe.encrypt_term(cipher, hash, iterations, passphrase, term_value) + + {:ok, result} + catch + _, _ -> + {:error, "Error during cipher operation"} + end + end + + def formatter(), do: RabbitMQ.CLI.Formatters.EncryptedConfValue + + def banner(_, _) do + "Encrypting value to be used in rabbitmq.conf..." + end + + def usage, + do: "encrypt_conf_value value passphrase [--cipher ] [--hash ] [--iterations ]" + + def usage_additional() do + [ + ["", "config value to encode"], + ["", "passphrase to use with the config value encryption key"], + ["--cipher ", "cipher suite to use"], + ["--hash ", "hashing function to use"], + ["--iterations ", "number of iteration to apply"] + ] + end + + def usage_doc_guides() do + [ + DocGuide.configuration() + ] + end + + def help_section(), do: :configuration + + def description(), do: "Encrypts a sensitive configuration value to be used in the advanced.config file" + + # + # 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) +end diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/encrypted_conf_value.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/encrypted_conf_value.ex new file mode 100644 index 000000000000..7eabc77b3a7a --- /dev/null +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/formatters/encrypted_conf_value.ex @@ -0,0 +1,26 @@ +## 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. + +## Prints values from a command as strings(if possible) +defmodule RabbitMQ.CLI.Formatters.EncryptedConfValue do + alias RabbitMQ.CLI.Core.Helpers + alias RabbitMQ.CLI.Formatters.FormatterHelpers + + @behaviour RabbitMQ.CLI.FormatterBehaviour + + def format_output(output, _) do + Helpers.string_or_inspect("encrypted:#{output}") + end + + def format_stream(stream, options) do + Stream.map( + stream, + FormatterHelpers.without_errors_1(fn el -> + format_output(el, options) + end) + ) + end +end From e6b073edafc4972c5d2f361af3c021502245c9e4 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 13 Aug 2024 14:26:02 -0400 Subject: [PATCH 3/6] More CLI commands for tagged values (cherry picked from commit e1490c6d9cba2b3490ce5077ce91a493012455fc) --- .../cli/ctl/commands/decode_command.ex | 6 +- .../commands/decrypt_conf_value_command.ex | 172 ++++++++++++++++++ .../cli/ctl/commands/encode_command.ex | 2 +- .../commands/encrypt_conf_value_command.ex | 2 +- .../ctl/encrypt_conf_value_command_test.exs | 78 ++++++++ 5 files changed, 255 insertions(+), 5 deletions(-) create mode 100644 deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decrypt_conf_value_command.ex create mode 100644 deps/rabbitmq_cli/test/ctl/encrypt_conf_value_command_test.exs diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex index 9f4211d89491..522c387e674e 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex @@ -117,7 +117,7 @@ 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, @@ -125,7 +125,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.DecodeCommand do def usage_additional() do [ - ["", "config value to decode"], + ["", "advanced.config (Erlang term) value to decode"], ["", "passphrase to use with the config value encryption key"], ["--cipher ", "cipher suite to use"], ["--hash ", "hashing function to use"], @@ -141,7 +141,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 diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decrypt_conf_value_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decrypt_conf_value_command.ex new file mode 100644 index 000000000000..6ac5958a96a1 --- /dev/null +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decrypt_conf_value_command.ex @@ -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 ] [--hash ] [--iterations ]" + + def usage_additional() do + [ + ["", "a double-quoted rabbitmq.conf string value to decode"], + ["", "passphrase to use with the config value encryption key"], + ["--cipher ", "cipher suite to use"], + ["--hash ", "hashing function to use"], + ["--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 diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex index 368bbd78c24b..03615420df84 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex @@ -130,7 +130,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do def usage_additional() do [ - ["", "config value to encode"], + ["", "value to encode, to be used in advanced.config"], ["", "passphrase to use with the config value encryption key"], ["--cipher ", "cipher suite to use"], ["--hash ", "hashing function to use"], diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex index f330f7732736..71809267bce7 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex @@ -2,7 +2,7 @@ ## 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. +## Copyright (c) 2007-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. defmodule RabbitMQ.CLI.Ctl.Commands.EncryptConfValueCommand do alias RabbitMQ.CLI.Core.{DocGuide, Helpers, Input} diff --git a/deps/rabbitmq_cli/test/ctl/encrypt_conf_value_command_test.exs b/deps/rabbitmq_cli/test/ctl/encrypt_conf_value_command_test.exs new file mode 100644 index 000000000000..e65f3b99a22a --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/encrypt_conf_value_command_test.exs @@ -0,0 +1,78 @@ +## 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-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. + +defmodule EncryptConfValueCommandTest do + use ExUnit.Case, async: false + + @command RabbitMQ.CLI.Ctl.Commands.EncryptConfValueCommand + + setup _context do + {:ok, + opts: %{ + cipher: :rabbit_pbe.default_cipher(), + hash: :rabbit_pbe.default_hash(), + iterations: :rabbit_pbe.default_iterations() + }} + end + + test "validate: providing exactly 2 positional arguments passes", context do + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: providing zero or one positional argument passes", context do + assert :ok == @command.validate([], context[:opts]) + assert :ok == @command.validate(["value"], context[:opts]) + end + + test "validate: providing three or more positional argument fails", context do + assert match?( + {:validation_failure, :too_many_args}, + @command.validate(["value", "secret", "incorrect"], context[:opts]) + ) + end + + test "validate: hash and cipher must be supported", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate( + ["value", "secret"], + Map.merge(context[:opts], %{cipher: :funny_cipher}) + ) + ) + + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate( + ["value", "secret"], + Map.merge(context[:opts], %{hash: :funny_hash}) + ) + ) + + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate( + ["value", "secret"], + Map.merge(context[:opts], %{cipher: :funny_cipher, hash: :funny_hash}) + ) + ) + + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: number of iterations must greater than 0", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: 0})) + ) + + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: -1})) + ) + + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end +end From b5c9064d4dae556c5272f2ede2d3549bb890a001 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 13 Aug 2024 14:29:14 -0400 Subject: [PATCH 4/6] Validation tests for the new command (cherry picked from commit 9dc899441f8aa6cf2b92e644c8796541693a07dc) --- .../ctl/decrypt_conf_value_command_test.exs | 83 +++++++++++++++++++ 1 file changed, 83 insertions(+) create mode 100644 deps/rabbitmq_cli/test/ctl/decrypt_conf_value_command_test.exs diff --git a/deps/rabbitmq_cli/test/ctl/decrypt_conf_value_command_test.exs b/deps/rabbitmq_cli/test/ctl/decrypt_conf_value_command_test.exs new file mode 100644 index 000000000000..e6dff24dbc21 --- /dev/null +++ b/deps/rabbitmq_cli/test/ctl/decrypt_conf_value_command_test.exs @@ -0,0 +1,83 @@ +## 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-2024 Broadcom. All Rights Reserved. The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries. All rights reserved. + +defmodule DecryptConfValueCommandTest do + use ExUnit.Case, async: false + @command RabbitMQ.CLI.Ctl.Commands.DecryptConfValueCommand + + setup _context do + {:ok, + opts: %{ + cipher: :rabbit_pbe.default_cipher(), + hash: :rabbit_pbe.default_hash(), + iterations: :rabbit_pbe.default_iterations() + }} + end + + test "validate: providing exactly 2 positional arguments passes", context do + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: providing no positional arguments fails", context do + assert match?( + {:validation_failure, {:not_enough_args, _}}, + @command.validate([], context[:opts]) + ) + end + + test "validate: providing one positional argument passes", context do + assert :ok == @command.validate(["value"], context[:opts]) + end + + test "validate: providing three or more positional argument fails", context do + assert match?( + {:validation_failure, :too_many_args}, + @command.validate(["value", "secret", "incorrect"], context[:opts]) + ) + end + + test "validate: hash and cipher must be supported", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate( + ["value", "secret"], + Map.merge(context[:opts], %{cipher: :funny_cipher}) + ) + ) + + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate( + ["value", "secret"], + Map.merge(context[:opts], %{hash: :funny_hash}) + ) + ) + + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate( + ["value", "secret"], + Map.merge(context[:opts], %{cipher: :funny_cipher, hash: :funny_hash}) + ) + ) + + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end + + test "validate: number of iterations must greater than 0", context do + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: 0})) + ) + + assert match?( + {:validation_failure, {:bad_argument, _}}, + @command.validate(["value", "secret"], Map.merge(context[:opts], %{iterations: -1})) + ) + + assert :ok == @command.validate(["value", "secret"], context[:opts]) + end +end From 75d6f50b3d62f25723a1e2bf9a55df3bdbbbde99 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 13 Aug 2024 14:31:55 -0400 Subject: [PATCH 5/6] Configuration value encryption CLI commands: unconditionally print stack traces (cherry picked from commit bd1e953b95a9f8a3e1bd0c5eb58fc888518f47ff) --- .../lib/rabbitmq/cli/ctl/commands/decode_command.ex | 2 ++ .../lib/rabbitmq/cli/ctl/commands/encode_command.ex | 3 +++ .../rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex | 2 ++ 3 files changed, 7 insertions(+) diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex index 522c387e674e..da124ae55564 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/decode_command.ex @@ -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 @@ -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 diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex index 03615420df84..8eb43e688c91 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encode_command.ex @@ -77,6 +77,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do {:ok, result} catch _, _ -> + IO.inspect(__STACKTRACE__) {:error, "Error during cipher operation"} end end @@ -99,6 +100,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do {:ok, result} catch _, _ -> + IO.inspect(__STACKTRACE__) {:error, "Error during cipher operation"} end end @@ -115,6 +117,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do {:ok, result} catch _, _ -> + IO.inspect(__STACKTRACE__) {:error, "Error during cipher operation"} end end diff --git a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex index 71809267bce7..914ad7debeb2 100644 --- a/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex +++ b/deps/rabbitmq_cli/lib/rabbitmq/cli/ctl/commands/encrypt_conf_value_command.ex @@ -97,6 +97,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncryptConfValueCommand do {:ok, result} catch _, _ -> + IO.inspect(__STACKTRACE__) {:error, "Error during cipher operation"} end end @@ -112,6 +113,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncryptConfValueCommand do {:ok, result} catch _, _ -> + IO.inspect(__STACKTRACE__) {:error, "Error during cipher operation"} end end From 3fd05414aba4a29034652a8bea4d5196744a4bf7 Mon Sep 17 00:00:00 2001 From: Michael Klishin Date: Tue, 13 Aug 2024 16:27:00 -0400 Subject: [PATCH 6/6] Allow for tagged values for a few more rabbitmq.conf settings (cherry picked from commit 8b90d4a27cd1979a47b9e1e89111485e01255b3c) --- deps/rabbit/priv/schema/rabbit.schema | 2 +- deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets | 4 ++-- .../priv/schema/rabbitmq_auth_backend_http.schema | 2 +- .../rabbitmq_auth_backend_http.snippets | 2 +- .../priv/schema/rabbitmq_management.schema | 4 ++-- .../priv/schema/rabbitmq_trust_store.schema | 2 +- .../config_schema_SUITE_data/rabbitmq_trust_store.snippets | 2 +- deps/rabbitmq_web_mqtt/priv/schema/rabbitmq_web_mqtt.schema | 2 +- .../config_schema_SUITE_data/rabbitmq_web_mqtt.snippets | 6 +++--- .../priv/schema/rabbitmq_web_stomp.schema | 2 +- .../config_schema_SUITE_data/rabbitmq_web_stomp.snippets | 6 +++--- 11 files changed, 17 insertions(+), 17 deletions(-) diff --git a/deps/rabbit/priv/schema/rabbit.schema b/deps/rabbit/priv/schema/rabbit.schema index 406ce3582abf..4d10cb206aad 100644 --- a/deps/rabbit/priv/schema/rabbit.schema +++ b/deps/rabbit/priv/schema/rabbit.schema @@ -706,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", [ diff --git a/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets b/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets index 247dd0f92f14..1a1b416e90e9 100644 --- a/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets +++ b/deps/rabbit/test/config_schema_SUITE_data/rabbit.snippets @@ -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">>, ".*"}]}]}]}], []}, @@ -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 diff --git a/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema b/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema index 150770ce2c18..b50013fb1651 100644 --- a/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema +++ b/deps/rabbitmq_auth_backend_http/priv/schema/rabbitmq_auth_backend_http.schema @@ -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}]}. diff --git a/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets b/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets index 9cd2ade9cb24..7d94d78bbc16 100644 --- a/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets +++ b/deps/rabbitmq_auth_backend_http/test/config_schema_SUITE_data/rabbitmq_auth_backend_http.snippets @@ -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 diff --git a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema index 1ee80516e200..83c32b3022ac 100644 --- a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema +++ b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema @@ -87,7 +87,7 @@ end}. {mapping, "management.ssl.cacertfile", "rabbitmq_management.ssl_config.cacertfile", [{datatype, string}, {validators, ["file_accessible"]}]}. {mapping, "management.ssl.password", "rabbitmq_management.ssl_config.password", - [{datatype, string}]}. + [{datatype, [tagged_binary, binary]}]}. {mapping, "management.ssl.verify", "rabbitmq_management.ssl_config.verify", [ {datatype, {enum, [verify_peer, verify_none]}}]}. @@ -295,7 +295,7 @@ end}. [{datatype, {enum, [true, false]}}]}. {mapping, "management.listener.ssl_opts.password", "rabbitmq_management.listener.ssl_opts.password", - [{datatype, string}]}. + [{datatype, [tagged_binary, binary]}]}. {mapping, "management.listener.ssl_opts.psk_identity", "rabbitmq_management.listener.ssl_opts.psk_identity", [{datatype, string}]}. diff --git a/deps/rabbitmq_trust_store/priv/schema/rabbitmq_trust_store.schema b/deps/rabbitmq_trust_store/priv/schema/rabbitmq_trust_store.schema index d3d33c251ccc..d9cc4a2afa51 100644 --- a/deps/rabbitmq_trust_store/priv/schema/rabbitmq_trust_store.schema +++ b/deps/rabbitmq_trust_store/priv/schema/rabbitmq_trust_store.schema @@ -124,7 +124,7 @@ end}. [{datatype, {enum, [true, false]}}]}. {mapping, "trust_store.ssl_options.password", "rabbitmq_trust_store.ssl_options.password", - [{datatype, string}]}. + [{datatype, [tagged_binary, binary]}]}. {mapping, "trust_store.ssl_options.psk_identity", "rabbitmq_trust_store.ssl_options.psk_identity", [{datatype, string}]}. diff --git a/deps/rabbitmq_trust_store/test/config_schema_SUITE_data/rabbitmq_trust_store.snippets b/deps/rabbitmq_trust_store/test/config_schema_SUITE_data/rabbitmq_trust_store.snippets index d45f48ecef45..b8d7f0457e3d 100644 --- a/deps/rabbitmq_trust_store/test/config_schema_SUITE_data/rabbitmq_trust_store.snippets +++ b/deps/rabbitmq_trust_store/test/config_schema_SUITE_data/rabbitmq_trust_store.snippets @@ -24,5 +24,5 @@ {url,"https://example.com"}, {ssl_options, [{certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, - {password,"i_am_password"}]}]}], + {password,<<"i_am_password">>}]}]}], [rabbitmq_trust_store]}]. diff --git a/deps/rabbitmq_web_mqtt/priv/schema/rabbitmq_web_mqtt.schema b/deps/rabbitmq_web_mqtt/priv/schema/rabbitmq_web_mqtt.schema index 91d6b1878239..e4afd579d4b7 100644 --- a/deps/rabbitmq_web_mqtt/priv/schema/rabbitmq_web_mqtt.schema +++ b/deps/rabbitmq_web_mqtt/priv/schema/rabbitmq_web_mqtt.schema @@ -56,7 +56,7 @@ {mapping, "web_mqtt.ssl.cacertfile", "rabbitmq_web_mqtt.ssl_config.cacertfile", [{datatype, string}, {validators, ["file_accessible"]}]}. {mapping, "web_mqtt.ssl.password", "rabbitmq_web_mqtt.ssl_config.password", - [{datatype, string}]}. + [{datatype, [tagged_binary, binary]}]}. {translation, "rabbitmq_web_mqtt.ssl_config", diff --git a/deps/rabbitmq_web_mqtt/test/config_schema_SUITE_data/rabbitmq_web_mqtt.snippets b/deps/rabbitmq_web_mqtt/test/config_schema_SUITE_data/rabbitmq_web_mqtt.snippets index f8ef2916f6ef..ab6735cbc830 100644 --- a/deps/rabbitmq_web_mqtt/test/config_schema_SUITE_data/rabbitmq_web_mqtt.snippets +++ b/deps/rabbitmq_web_mqtt/test/config_schema_SUITE_data/rabbitmq_web_mqtt.snippets @@ -85,7 +85,7 @@ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, - {password,"changeme"}]}]}], + {password,<<"changeme">>}]}]}], [rabbitmq_web_mqtt]}, {ssl, @@ -108,7 +108,7 @@ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, - {password,"changeme"}, + {password,<<"changeme">>}, {versions,['tlsv1.2','tlsv1.1']} ]}]}], @@ -145,7 +145,7 @@ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, - {password,"changeme"}, + {password,<<"changeme">>}, {honor_cipher_order, true}, {honor_ecc_order, true}, diff --git a/deps/rabbitmq_web_stomp/priv/schema/rabbitmq_web_stomp.schema b/deps/rabbitmq_web_stomp/priv/schema/rabbitmq_web_stomp.schema index 273d30cb3a2b..c16e74837563 100644 --- a/deps/rabbitmq_web_stomp/priv/schema/rabbitmq_web_stomp.schema +++ b/deps/rabbitmq_web_stomp/priv/schema/rabbitmq_web_stomp.schema @@ -65,7 +65,7 @@ {mapping, "web_stomp.ssl.cacertfile", "rabbitmq_web_stomp.ssl_config.cacertfile", [{datatype, string}, {validators, ["file_accessible"]}]}. {mapping, "web_stomp.ssl.password", "rabbitmq_web_stomp.ssl_config.password", - [{datatype, string}]}. + [{datatype, [tagged_binary, binary]}]}. {translation, "rabbitmq_web_stomp.ssl_config", diff --git a/deps/rabbitmq_web_stomp/test/config_schema_SUITE_data/rabbitmq_web_stomp.snippets b/deps/rabbitmq_web_stomp/test/config_schema_SUITE_data/rabbitmq_web_stomp.snippets index 8a41ce031b90..fc901e2d05a4 100644 --- a/deps/rabbitmq_web_stomp/test/config_schema_SUITE_data/rabbitmq_web_stomp.snippets +++ b/deps/rabbitmq_web_stomp/test/config_schema_SUITE_data/rabbitmq_web_stomp.snippets @@ -79,7 +79,7 @@ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, - {password,"changeme"}]}]}], + {password,<<"changeme">>}]}]}], [rabbitmq_web_stomp]}, {ssl, @@ -99,7 +99,7 @@ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, - {password,"changeme"}, + {password,<<"changeme">>}, {versions,['tlsv1.2','tlsv1.1']} ]}]}], @@ -136,7 +136,7 @@ {certfile,"test/config_schema_SUITE_data/certs/cert.pem"}, {keyfile,"test/config_schema_SUITE_data/certs/key.pem"}, {cacertfile,"test/config_schema_SUITE_data/certs/cacert.pem"}, - {password,"changeme"}, + {password,<<"changeme">>}, {honor_cipher_order, true}, {honor_ecc_order, true},