RabbitMQ 鉴权、授权、权限访问

鉴权、授权、访问控制

概览

这部分描述了 RabbitMQ 中的鉴权和授权特性。以及操作着对系统的访问控制。不同的用户可以被授予特定的 vitual host 访问。在每个虚拟主机的具体许可也可以限制。

RabbitMQ 支持两种主要的鉴权机制以及几个认证和授权后端。

基于密码 的鉴权有配套的指南。 关于支持 TLS 的内容也包含在专门的指南里。

指南中讨论的其他话题包括:

  • 默认虚拟主机和用户
  • 对默认用户的连接限制
  • 鉴权和授权失败的故障排除

相关术语和定义

鉴权和授权经常被混淆或者互换使用。在 RabbitMQ 中这两个是分开的,互换使用是错误的。简单来说,鉴权是验证用户是谁,而授权是决定用户允许和不被允许做的。

默认虚拟主机和用户

当服务器第一次启动,检测到数据库没有初始化或者被删掉了,它将会使用以下资源初始化一个新的数据库:

  • 一个虚拟主机,名字为 /;
  • 名为 guest,默认密码为 guest 的用户,并赋予 / 虚拟主机的全部权限

这里建议删除 guest 用户或者至少改变其密码为一个合理安全生成的不为大众知道的值。

鉴权:你说你是谁?

在一个应用连接上 RabbitMQ 后,有任何操作之前,必须要经过鉴权,也就是提供并证明它的身份。有了证明的身份,RabbitMQ 节点会查看它的权限并授权访问相应的资源,如虚拟主机,队列,交换机等等。

两个基本的鉴权客户端方式是使用用户密码和 X.509 证书。用户密码方式可以使用多种鉴权后台来验证凭证。

鉴权失败的连接会被关闭,并在服务器日志中记录错误消息。

使用 X.509 证书方式的连接鉴权需要使用内置的插件,rabbitmq-auth-mechanism-ssl,所有必须启用该插件,客户端也必须配置为使用外部机制。在该机制下,任何提供密码的的客户端将被忽略。

“guest” 用户只能本地访问

默认情况下,guest 用户禁止同步远程主机连接;它仅可以通过环回接口(如:localhost)连接。这适用于任何协议的连接。任何其他用户不会有这样的限制(默认)。

在生产系统中,建议通过创建新用户,或者一系列只有访问相应虚拟主机权限的用户。可以通过 CLI tools ,HTTP API 或者 definitions import

这是通过配置文件中的 loopback_users 项配置的。

通过设置 loopback_usersnone 可以使 guest 用户进行远程连接。

一个允许 guest 用户远程连接的最简化RabitMQ config file看起来是这样的:

# DANGER ZONE!
#
# allowing remote connections for default user is highly discouraged
# as it dramatically decreases the security of the system. Delete the user
# instead and create a new one with generated secure credentials.
loopback_users = none

或者经典的配置文件格式(rabbitmq.config):

%% DANGER ZONE!
%%
%% Allowing remote connections for default user is highly discouraged
%% as it dramatically decreases the security of the system. Delete the user
%% instead and create a new one with generated secure credentials.
[{rabbit, [{loopback_users, []}]}].

授权:权限如何工作

当一个 RabbitMQ 客户端建立连接到服务端,然后鉴权,它指定了准备操作的虚拟主机。此时执行第一级访问控制,服务器检查用户是否有访问虚拟主机的权限,否则拒绝连接尝试。

资源,如:交换机和队列,是特定虚拟主机中的命名实体;每个虚拟主机下,即使名字相同也是不同的资源。当在资源上执行某些操作时,执行第二级访问控制。

RabbitMQ 在资源操作上区分配置。配置操作可以创建或者销毁资源或者改变它们的行为。写操作将消息注入资源中。读操作从资源中获取消息。

用户想在在资源上执行操作就必须被赋予恰当的权限。以下的表格显示了执行权限检查的所有 AMQP 命令在不同资源的类型时需要权限。

AMQP 0-9-1 Operation configure write read
exchange.declare (passive=false) exchange
exchange.declare (passive=true)
exchange.declare (with [AE](ae.html)) exchange exchange (AE) exchange
exchange.delete exchange
queue.declare (passive=false) queue
queue.declare (passive=true)
queue.declare (with [DLX](dlx.html)) queue exchange (DLX) queue
queue.delete queue
exchange.bind exchange (destination) exchange (source)
exchange.unbind exchange (destination) exchange (source)
queue.bind queue exchange
queue.unbind queue exchange
basic.publish exchange
basic.get queue
basic.consume queue
queue.purge queue
说明:
  • passive的理解:passive意思是被动的,当设置为true,那么交换器或者队列存在,则返回true,否则会抛出异常,但是不会创建新的交换器或队列;
  • AE:Alternate Exchanges,备用交换器
  • DLX:Dead Letter Exchanges,死信交换器

权限在每个虚拟主机的基础下,由三个正则表达式表示 ---- 依次代表配置,写和读。用户被授予对名称与正则表达式匹配的所有资源进行操作的相应权限。(注:为方便起见,RabbitMQ 在执行权限检查时将默认交换机的空名称映射为 amq.default。)

正则表达式 ^$ 只匹配空字符串,实际上禁止用户对任何资源执行操作。标准的 AMQP 资源名称以 amp 为前缀,服务器生成的名字以 amp.gen 为前缀。举例来说,'^(amq.gen.*|amq.default)$' 表示用户拥有服务器自动生成的名字,以及默认交换机的权限。空字符串 '''^$' 的同义词,在权限限制上效果是一样的。

RabbitMQ 在每个连接和每个 channel 的基础上缓存访问控制的检查。因此改变用户的权限会在用户重新连接后才能生效。

关于设置访问控制的详细信息,请查阅 rabbitmqctl 手册访问控制部分 部分。

用户标签和管理界面访问

除了上面提到的权限,用户可以有与之相关联的标签。目前只有管理界面的访问由用户标签控制。

便签通过 rabbitmqctl 管理。新创建的用户默认没有任何标签。

请参考管理插件指南来学习更多关于标签的情况,如具体支持的标签内容,以及它们如何限制管理界面的访问。

Topic 授权(Topic Authorisation)

在 3.7.0 的版本,RabbitMQ 支持了为 topic 交换机准备的 topic 授权。发布授权实施时,主题交换机的消息所带的路由键也会进行匹配(如:在 RabbitMQ 默认的后台授权中,路由键与正则表达式比对来决定消息是否可以向下路由。主题授权以 STOMP 和 MQTT 协议为目标,这些协议围绕主题构建,并在幕后使用了主题交换机。

主题授权是对发布者现有检查之外的一层。发布一个消息到主题类型的交换机将会通过 basic.publish 和 路由键两层检查。在前面一层拒绝访问时后面这一层就不会用到了。

主题授权也可以对主题消费者强制执行。请注意,对于不同的协议,它的工作方式也不同。主题授权的概念仅仅会在主题导向的协议,如 MQTT 和 STOMP 下才会有意义。 举例来说,在 AMQP 0-9-1 下,消费者从队列中消费,因此标准资源权限适用。额外的,如果配置了任何主题权限,在 AMQP 0-9-1 协议下的主题交换机和队列/交换机之间绑定路由键也会检查。关于 RabbitMQ 如何处理主题相关授权的更多信息,请查阅 STOMPMQTT 指导文档。

如果没有定义主题权限,在使用默认的授权后端时,发布消息到主题交换机或者从一个主题下消费是不要授权的(在新安装的 RabbitMQ 服务就会出现这样的情况)。此时,主题授权是可选的:你不需要把任何交换机列入白名单。如果要使用主题授权,你需要为一个或多个交换机选择并定义主题权限。详情见 rabbitmqctl 手册。

内部(默认)授权后端支持权限模式的可变扩展。支持三种变量:usernamevhostclient_id。主要 client_Id 仅适用于 MQTT 协议。例如:如果 tonyg 是连接的用户,许可 ^{username}-.* 就是 ^tonyg-.*

如果使用了其他的授权后端(如 LDAP,HTTP,AMQP),请参阅相关后端的文档。

如果使用了一个自定义的授权后端,主题授权通过实现 rabbit_authz_backend 行为的 check_topic_access 回调来实施。

可替代的鉴权与授权后端

鉴权和授权是可插拔的。插件可以提供:

  • 鉴权(“authn”)后端
  • 授权(“authz”)后端

一个插件可能这两者都提供。比如内置的 LDAP 和 HTTP 后端就是。

有些插件,如 Source IP range one,只提供授权后端。鉴权部分由内部数据、LDAP 等处理。

组合后端

可以使用 auth_backends 配置密钥为 authnauthz 使用多个后端。当使用多个鉴权后端时,后端链中最先返回的肯定结果认为是最终的结果。这个不应该与混合后端相混淆(如:使用 LDAP 鉴权,并使用内置后端授权)。

以下的 RabbitMQ 配置示例仅使用了内置后端(这也是默认情况):

# rabbitmq.conf
#
# 1 here is a backend name. It can be anything.
# Since we only really care about backend
# ordering, we use numbers throughout this guide.
#
# "internal" is an alias for rabbit_auth_backend_internal
auth_backends.1 = internal

或者使用经典的配置格式:

[{rabbit, [
            {auth_backends, [rabbit_auth_backend_internal]}
          ]
}].

上面的示例中使用了一个别名,internal ,它是 rabbit_auth_backend_internal 的别名。下面是可以使用的别名:

  • internal -> rabbit_auth_backend_internal
  • ldap -> rabbit_auth_backend_ldap (LDAP 插件)
  • http -> rabbit_auth_backend_http (HTTP auth backend plugin)
    amqp -> rabbit_auth_backend_amqp (AMQP 0-9-1 auth backend plugin)
    dummy -> rabbit_auth_backend_dummy

当使用第三方插件,必须提供完整的模块名称。

下面的 RabbitMQ 配置示例使用了 LDAP 后端鉴权和鉴权。这将不会使用内置的数据库:

auth_backends.1 = ldap

或者经典配置格式:

[{rabbit, [
            {auth_backends, [rabbit_auth_backend_ldap]}
          ]
}].

这将会先检查 LDAP,如果用户无法通过 LDAP 鉴权则会使用内部的数据库:

auth_backends.1 = ldap
auth_backends.2 = internal

或者经典配置格式:

[{rabbit, [
            {auth_backends, [rabbit_auth_backend_ldap, rabbit_auth_backend_internal]}
          ]
}].

类似于上面,但会退回到使用 HTTP

# rabbitmq.conf
#
auth_backends.1 = ldap
# uses module name instead of a short alias, "http"
auth_backends.2 = rabbit_auth_backend_http

# See HTTP backend docs for details
auth_http.user_path = http://my-authenticator-app/auth/user
auth_http.vhost_path = http://my-authenticator-app/auth/vhost
auth_http.resource_path = http://my-authenticator-app/auth/resource
auth_http.topic_path = http://my-authenticator-app/auth/topic

或者经典配置格式:

[{rabbit, [
            {auth_backends, [rabbit_auth_backend_ldap, rabbit_auth_backend_http]}
          ]
 },
 %% See HTTP backend docs for details
 {rabbitmq_auth_backend_http,
   [{user_path,     "http://my-authenticator-app/auth/user"},
    {vhost_path,    "http://my-authenticator-app/auth/vhost"},
    {resource_path, "http://my-authenticator-app/auth/resource"},
    {topic_path,    "http://my-authenticator-app/auth/topic"}]}].

下面的配置示例将会使用内置数据库鉴权以及 source IP range backend 授权:

# rabbitmq.conf
#
auth_backends.1.authn = internal
# uses module name because this backend is from a 3rd party
auth_backends.1.authz = rabbit_auth_backend_ip_range

或者经典配置格式:

[{rabbit, [
            {auth_backends, [{rabbit_auth_backend_internal, rabbit_auth_backend_ip_range}]}
          ]
}].

下面是的示例将会使用 LDAP 作为鉴权,内置后端作为鉴权:

# rabbitmq.conf
#
auth_backends.1.authn = ldap
auth_backends.1.authz = internal

或者经典配置格式:

[{rabbit, [
            {auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal}]
          ]}].

下面的示例更加高级。它将会先检查 LDAP。如果用户在 LDAP 中找到,然后会校验密码,以及后续的授权检查会比对内部数据库执行(因此在 LDAP 的用户必须也在内部数据库存储,但密码可以没有)。如果用户没在 LDAP 中找到,然后会只使用内部数据进行第二次尝试。

# rabbitmq.conf
#
auth_backends.1.authn = ldap
auth_backends.1.authz = internal
auth_backends.2       = internal

或者经典配置格式:

[{rabbit, [
            {auth_backends, [{rabbit_auth_backend_ldap, rabbit_auth_backend_internal},
                             rabbit_auth_backend_internal]}
          ]
}].

鉴权机制

RabbitMQ 支持多种 SASL 鉴权机制。服务器中内置了三种:PLAINAMQPLAINRABBIT-CR-DEMO,以及一种通过插件方式的 EXTERNAL

更多鉴权机制可以通过插件提供。查阅插件开发指南获取更多关于通用插件开发的信息。

内置鉴权机制

内置机制有:

机制类型 描述
PLAIN SASL PLAIN 鉴权。在 RabbitMQ 服务器和客户端都是默认开启,大部分其他客户端也是该默认设置。
AMQPPLAIN 为像后兼容的非标准版 PLAIN,这个在 RabbitMQ 服务端默认开启。
EXTERNAL 鉴权发生在使用带外机制,如 x509 证书的对等验证客户端 IP 地址范围,或者类似的。这样的机制通用由 RabbitMQ 插件提供。
RABBIT-CR-DEMO 展示质询-响应鉴权的非标准机制。这个机制安全性与 PLAIN 相等,在 RabbitMQ 服务端默认不开启。

服务器配置机制

RabbitMQ 应用中配置变量 auth_mechanisms 决定了提供哪一种已安装的机制给连接的客户端。该变量应该是相应机制名称的原子列表,如:默认的 ['PLAIN', 'AMQPLAIN']。该服务端的列表不代表特定的顺序。文档

客户端配置机制

应用程序必须可选的加入不同的鉴权机制才能使用,如 EXTERNAL。

java 版配置机制

Java 客户端不会使用默认的包 javax.security.sasl ,因为这在非 Oracle JDK 是不可预测的,而且在 Android 上是完全缺失的。etc.

.NET 版配置机制

略…

erlang 版配置机制

略…

鉴权故障解决

服务器日志 包含鉴权请求失败的尝试记录:

2019-03-25 12:28:19.047 [info] <0.1613.0> accepting AMQP connection <0.1613.0> (127.0.0.1:63839 -> 127.0.0.1:5672)
2019-03-25 12:28:19.056 [error] <0.1613.0> Error on AMQP connection <0.1613.0> (127.0.0.1:63839 -> 127.0.0.1:5672, state: starting):
PLAIN login refused: user 'user2' - invalid credentials
2019-03-25 12:28:22.057 [info] <0.1613.0> closing AMQP connection <0.1613.0> (127.0.0.1:63839 -> 127.0.0.1:5672)

使用 证书 连接鉴权失败将会有不一样的记录。查看 TLS 故障解决指导。

rabbitmqctl authenticate_user 可以用来测试用户名密码方式的鉴权。

rabbitmqctl authenticate_user 'a-username' 'a/password'

如果鉴权成功,它将会退出,代码值为零。如果出现故障,将使用非零代码值退出,并打印故障错误消息。

rabbitmqctl authenticate_user 会使用节点命令行的内部 API 通讯连接尝试使用用户名密码进行鉴权。
该连接被认为是可信的。如果不是这样,它的流量可以使用 TLS 加密。

根据 AMQP 0-9-1 规范,鉴权失败会导致服务器立即关闭 TCP 连接。但是,通过使用基于 AMQP 0-9-1 扩展的 鉴权失败通知 插件,RabbitMQ 客户端可以选择获取更加具体的通知。现代客户端库对用户透明的支持该扩展:无需必要的配置,鉴权失败将导致可见的错误返回、异常或其他方式来传达特定编程语言或环境中使用的问题。

授权故障解决

rabbtmqctl list_permissions 可以用来检查用户在给定虚拟主机的权限:

rabbitmqctl list_permissions --vhost /
# => Listing permissions for vhost "/" ...
# => user    configure   write   read
# => user2   .*  .*  .*
# => guest   .*  .*  .*
# => temp-user   .*  .*  .*

rabbitmqctl list_permissions --vhost gw1
# => Listing permissions for vhost "gw1" ...
# => user    configure   write   read
# => guest   .*  .*  .*
# => user2   ^user2  ^user2  ^user2

server logs 将会包含关于授权操作失败的记录。比如:一个用户在一个虚拟主机中没有配置任何权限:

2019-03-25 12:26:16.301 [info] <0.1594.0> accepting AMQP connection <0.1594.0> (127.0.0.1:63793 -> 127.0.0.1:5672)
2019-03-25 12:26:16.309 [error] <0.1594.0> Error on AMQP connection <0.1594.0> (127.0.0.1:63793 -> 127.0.0.1:5672, user: 'user2', state: opening):
access to vhost '/' refused for user 'user2'
2019-03-25 12:26:16.310 [info] <0.1594.0> closing AMQP connection <0.1594.0> (127.0.0.1:63793 -> 127.0.0.1:5672, vhost: 'none', user: 'user2')

授权失败(权限违规)也会记录:

2019-03-25 12:30:05.209 [error] <0.1627.0> Channel error on connection <0.1618.0> (127.0.0.1:63881 -> 127.0.0.1:5672, vhost: 'gw1', user: 'user2'), channel 1:
operation queue.declare caused a channel exception access_refused: access to queue 'user3.q1' in vhost 'gw1' refused for user 'user2'

翻译自:

  1. https://www.rabbitmq.com/access-control.html

猜你喜欢

转载自blog.csdn.net/qq_35958788/article/details/92964579
今日推荐