More CLI commands for tagged values

This commit is contained in:
Michael Klishin 2024-08-13 14:26:02 -04:00
parent c2fdd73c4b
commit e1490c6d9c
5 changed files with 255 additions and 5 deletions

View File

@ -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
[
["<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"],
@ -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

View File

@ -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

View File

@ -130,7 +130,7 @@ defmodule RabbitMQ.CLI.Ctl.Commands.EncodeCommand do
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"],

View File

@ -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}

View File

@ -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