Skip to content

Commit

Permalink
Introduce a CLI command that grants permissions to all virtual hosts
Browse files Browse the repository at this point in the history
Closes #1000
  • Loading branch information
michaelklishin committed Feb 6, 2023
1 parent 3b5ec6a commit ab99ccf
Show file tree
Hide file tree
Showing 4 changed files with 206 additions and 1 deletion.
7 changes: 6 additions & 1 deletion deps/rabbit/src/rabbit_auth_backend_internal.erl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
-export([add_user/3, add_user/4, add_user/5, delete_user/2, lookup_user/1, exists/1,
change_password/3, clear_password/2,
hash_password/2, change_password_hash/2, change_password_hash/3,
set_tags/3, set_permissions/6, clear_permissions/3, clear_permissions_for_vhost/2,
set_tags/3, set_permissions/6, clear_permissions/3, clear_permissions_for_vhost/2, set_permissions_globally/5,
set_topic_permissions/6, clear_topic_permissions/3, clear_topic_permissions/4, clear_topic_permissions_for_vhost/2,
add_user_sans_validation/3, put_user/2, put_user/3,
update_user/5,
Expand Down Expand Up @@ -536,6 +536,11 @@ clear_permissions(Username, VirtualHost, ActingUser) ->
clear_permissions_for_vhost(VirtualHost, _ActingUser) ->
rabbit_db_user:clear_matching_user_permissions('_', VirtualHost).

set_permissions_globally(Username, ConfigurePerm, WritePerm, ReadPerm, ActingUser) ->
VirtualHosts = rabbit_vhost:list_names(),
[set_permissions(Username, VH, ConfigurePerm, WritePerm, ReadPerm, ActingUser) || VH <- VirtualHosts],
ok.

set_topic_permissions(Username, VirtualHost, Exchange, WritePerm, ReadPerm, ActingUser) ->
rabbit_log:debug("Asked to set topic permissions on exchange '~ts' for "
"user '~ts' in virtual host '~ts' to '~ts', '~ts'",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
## 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 VMware, Inc. or its affiliates. All rights reserved.

defmodule RabbitMQ.CLI.Ctl.Commands.SetPermissionsGloballyCommand do
alias RabbitMQ.CLI.Core.{DocGuide, ExitCodes, Helpers}

@behaviour RabbitMQ.CLI.CommandBehaviour

def merge_defaults(args, opts) do
{args, Map.merge(%{vhost: "/"}, opts)}
end

def validate([], _) do
{:validation_failure, :not_enough_args}
end

def validate([_ | _] = args, _) when length(args) < 4 do
{:validation_failure, :not_enough_args}
end

def validate([_ | _] = args, _) when length(args) > 4 do
{:validation_failure, :too_many_args}
end

def validate(_, _), do: :ok

use RabbitMQ.CLI.Core.RequiresRabbitAppRunning

def run([user, conf, write, read], %{node: node_name}) do
:rabbit_misc.rpc_call(
node_name,
:rabbit_auth_backend_internal,
:set_permissions_globally,
[user, conf, write, read, Helpers.cli_acting_user()]
)
end

def output({:error, {:no_such_user, username}}, %{node: node_name, formatter: "json"}) do
{:error,
%{"result" => "error", "node" => node_name, "message" => "User #{username} does not exist"}}
end

def output({:error, {:no_such_user, username}}, _) do
{:error, ExitCodes.exit_nouser(), "User #{username} does not exist"}
end

use RabbitMQ.CLI.DefaultOutput

def usage, do: "set_permissions_globally <username> <conf> <write> <read>"

def usage_additional() do
[
["<username>", "Self-explanatory"],
["<conf>", "Configuration permission pattern"],
["<write>", "Write permission pattern"],
["<read>", "Read permission pattern"]
]
end

def usage_doc_guides() do
[
DocGuide.access_control(),
DocGuide.virtual_hosts()
]
end

def help_section(), do: :access_control
def description(), do: "Sets user permissions for all virtual hosts."

def banner([user | _], _opts),
do: "Setting permissions for user \"#{user}\" in all virtual hosts ..."
end
115 changes: 115 additions & 0 deletions deps/rabbitmq_cli/test/ctl/set_permissions_globally_command_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
## 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-2020 VMware, Inc. or its affiliates. All rights reserved.

defmodule SetPermissionsGloballyCommandTest do
use ExUnit.Case, async: false
import TestHelper

@command RabbitMQ.CLI.Ctl.Commands.SetPermissionsGloballyCommand

@vhost1 "test1"
@vhost2 "test2"
@vhost3 "test3"

@user "guest"

setup_all do
RabbitMQ.CLI.Core.Distribution.start()

for v <- [@vhost1, @vhost2, @vhost3], do: add_vhost(v)

on_exit([], fn ->
for v <- [@vhost1, @vhost2, @vhost3], do: delete_vhost(v)
end)

:ok
end

setup context do
on_exit(context, fn ->
set_permissions_globally(context[:user], [".*", ".*", ".*"])
end)

{
:ok,
opts: %{
node: get_rabbit_hostname()
}
}
end

test "merge_defaults: defaults can be overridden" do
assert @command.merge_defaults([], %{}) == {[], %{vhost: "/"}}
assert @command.merge_defaults([], %{vhost: "non_default"}) == {[], %{vhost: "non_default"}}
end

test "validate: wrong number of arguments leads to an arg count error" do
assert @command.validate([], %{}) == {:validation_failure, :not_enough_args}
assert @command.validate(["insufficient"], %{}) == {:validation_failure, :not_enough_args}
assert @command.validate(["not", "enough"], %{}) == {:validation_failure, :not_enough_args}

assert @command.validate(["not", "quite", "enough"], %{}) ==
{:validation_failure, :not_enough_args}

assert @command.validate(["this", "is", "way", "too", "many"], %{}) ==
{:validation_failure, :too_many_args}
end

@tag user: @user
test "run: a well-formed, host-specific command returns okay", context do
assert @command.run(
[context[:user], "^#{context[:user]}-.*", ".*", ".*"],
context[:opts]
) == :ok

p1 = Enum.find(list_permissions(@vhost1), fn x -> x[:user] == context[:user] end)
p2 = Enum.find(list_permissions(@vhost2), fn x -> x[:user] == context[:user] end)
p3 = Enum.find(list_permissions(@vhost3), fn x -> x[:user] == context[:user] end)

assert p1[:configure] == "^#{context[:user]}-.*"
assert p2[:configure] == "^#{context[:user]}-.*"
assert p3[:configure] == "^#{context[:user]}-.*"
end

test "run: throws a badrpc when instructed to contact an unreachable RabbitMQ node" do
opts = %{node: :jake@thedog, timeout: 200}

assert match?({:badrpc, _}, @command.run([@user, ".*", ".*", ".*"], opts))
end

@tag user: "interloper"
test "run: an invalid user returns a no-such-user error", context do
assert @command.run(
[context[:user], "^#{context[:user]}-.*", ".*", ".*"],
context[:opts]
) == {:error, {:no_such_user, context[:user]}}
end

@tag user: @user
test "run: invalid regex patterns returns an error", context do
assert @command.run(
[context[:user], "^#{context[:user]}-.*", ".*", "*"],
context[:opts]
) == {:error, {:invalid_regexp, '*', {'nothing to repeat', 0}}}

# asserts that the failed command didn't change anything
p1 = Enum.find(list_permissions(@vhost1), fn x -> x[:user] == context[:user] end)
p2 = Enum.find(list_permissions(@vhost2), fn x -> x[:user] == context[:user] end)
p3 = Enum.find(list_permissions(@vhost3), fn x -> x[:user] == context[:user] end)

assert p1 == [user: context[:user], configure: ".*", write: ".*", read: ".*"]
assert p2 == [user: context[:user], configure: ".*", write: ".*", read: ".*"]
assert p3 == [user: context[:user], configure: ".*", write: ".*", read: ".*"]
end

@tag user: @user
test "banner", context do
vhost_opts = Map.merge(context[:opts], %{vhost: context[:vhost]})

assert @command.banner([context[:user], "^#{context[:user]}-.*", ".*", ".*"], vhost_opts) =~
~r/Setting permissions for user \"#{context[:user]}\" in all virtual hosts \.\.\./
end
end
10 changes: 10 additions & 0 deletions deps/rabbitmq_cli/test/test_helper.exs
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,16 @@ defmodule TestHelper do
])
end

def set_permissions_globally(user, [conf, write, read]) do
:rpc.call(get_rabbit_hostname(), :rabbit_auth_backend_internal, :set_permissions_globally, [
user,
conf,
write,
read,
"acting-user"
])
end

def list_policies(vhost) do
:rpc.call(get_rabbit_hostname(), :rabbit_policy, :list_formatted, [vhost])
end
Expand Down

0 comments on commit ab99ccf

Please sign in to comment.