Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

rpc支持身份认证 #166

Closed
shengofsun opened this issue Aug 20, 2018 · 2 comments
Closed

rpc支持身份认证 #166

shengofsun opened this issue Aug 20, 2018 · 2 comments
Labels
component/auth authorization & anthentication

Comments

@shengofsun
Copy link
Contributor

shengofsun commented Aug 20, 2018

总体过程

身份认证可以用流行的Kerberos来实现。需要引用的第三方项目有:

  • Mit Kerberos
  • cyrus sasl: 虽然真正认证用的是kerberos的方案,但流程应该采用更通用的SASL。这样代码可以更通用,也更方便兼容其他的认证方案。

关于Kerberos和sasl的更详细介绍可以参考这里

为了嵌入身份认证,需要修改rDSN的rpc_engine模块,从而使得socket的发起方和接收方在身份认证成功后才能将rpc_session建立成功。

身份认证的过程

使用sasl+kerberos的身份认证的流程如下:

                      client               server
                         | --- SASL_MECH --> |
                         | <-- SASL_MECH --- |
                         |                   |
                         | - SASL_SEL_MECH ->|
                         | <- SASL_SEL_OK ---|
                         |                   |
                         | --- SASL_INIT --> |
                         |                   |
                         | <-- SASL_CHAL --- |
                         | --- SASL_RESP --> |
                         |                   |
                         |      .....        |
                         |                   |
                         | <-- SASL_CHAL --- |
                         | --- SASL_RESP --> |
                         |                   | (authentication will succeed
                         |                   |  if all chanllenges passed)
                         | <-- SASL_SUCC --- |
(client won't response   |                   |
if servers says ok)      |                   |
                         | --- RPC_CALL ---> |
                         | <-- RPC_RESP ---- |

上述流程可以简要概括为:

  1. client和server通过两轮rpc来交换身份认证的方式
  2. client首先携带一些信息(initialize response)发起认证
  3. server端在收到初始信息后,开始发起challenge;然后client响应server的challenge。这样的过程会进行数次。
  4. 如果client能接收server的所有challenge, server会率先认为认证通过,并通知client。然后client收到通知后,标记会话认证成功,并不做任何响应。
  5. 然后client就可以发送正常的rpc消息给server了。

如何实现

在实现身份认证的时候,我们遵循了rdsn中"client和server一问一答"的rpc模型,这样就可以复用rpc_engine的代码了。

具体来看,我们定义了几个类型和结构体:

DEFINE_TASK_CODE_RPC(RPC_NEGOTIATION,...)

enum negotiation_status {
    INVALID = 0,
    SASL_LIST_MECHANISMS,
    SASL_LIST_MECHANISMS_RESP,
    SASL_SELECT_MECHANISMS,
    SASL_SELECT_MECHANISMS_OK,
    SASL_INITIATE,
    SASL_CHALLENGE,
    SASL_RESPONSE,
    SASL_SUCC,
    SASL_AUTH_FAIL
}

struct negotiation_message {
    1: negotiation_status status;
    2: dsn.blob msg;
}

client和server端就用定义好的rpc code和IDL结构体进行交互。具体要点包括:

  1. 具体的认证消息都是negotiation_message。所有的message都封装在rpc_message中。
  2. client发到server端的全是rpc_request, server发到client端的都是rpc_response。注意这里一定要和SASL的(challenge, response)序对给区分开。在SASL中,server发送CHALLENGE,client响应RESPONSE。这和我们的rpc_message的类型是相反的。

细节要点

在设计并实现身份认证的时候,有些细节要点是必须得注意的:

  1. 支持认证方式的协商, 这样可以兼容其他的认证方案。尤其是公司内部可能有一些内部的认证方式。
  2. 在把认证过程集成到现有的server中时,需要考虑server的平滑升级,以及客户端的兼容性问题。
  3. kerberos为身份认证所颁发的ticket存在期限。如果过期该怎么处理。
  4. 为了应对kerberos的证书颁发模块出问题,应该允许临时关闭认证。

接下来我们会逐个介绍这些问题。

认证方式的协商

认证方式的协商是通过两轮RPC来进行的:

  • client向server询问server端支持的认证方式;server端回复(当前仅支持GSSAPI)。
  • client向server发送自己选择的认证方式,从而双方达成一致。

平滑升级

平滑升级server端时候需要考虑的问题包括:

  1. 新版本的客户端访问旧版本的server。 即客户端要求认证,但server端完全不知道什么是认证。
  2. 旧版本的客户端访问新版本的server。即客户端完全不知道什么是认证,但server要求认证。
  3. server端升级后,其他语言的SDK没有升级(java, python, nodejs....)。同样是客户端完全不知道什么是认证,但server要求认证。

为了保证这些情况能够被处理,我们需要引入一个额外的配置项mandatory_authentication来表示是不是强制认证。如果认证是强制选项,那么对于不处理认证消息的对端,要强制关闭会话;反之,就需要跳过所有的认证过程,直接允许会话可以直接交换上层的RPC数据。

使用Kerberos时候的若干问题

kerberos相关文件的管理

在使用Kerberos上,有几个文件/数据是需要管理好的:

  • kerberos的配置文件,需要记录KDC的URL,以及ticket的相关参数。
  • keytab文件。客户端需要这个文件来获取授权,访问server;server端需要这个文件验证客户端
  • credential cache, 客户端获取到相关的授权后,是存在一个叫做credential cache(ccache)的地方的

默认情况下,这些数据全部以文件的形式保存在固定的路径下。但当我们部署自己的应用时,一定是希望这些路径是可配置的。kerberos库允许我们以设置环境变量的方式来指定这些文件的存取路径。在pegasus身份认证的实现中,kerberos配置和keytab是通过配置文件来指定的。而credential cache是保存在进程的虚拟内存中的。

TGT的更新

kerberos给客户端的授权就存放在上文所说的credential cache中(因为TGT就是credential的一种);并且TGT是存在有效期的。我们需要使用Kerberos的相关API来获取TGT的有效期,并且要在到期前使用相关API来更新它。

Kerberos接口的线程安全

MIT kerberos提供的接口都是线程安全的,放心使用。

认证的临时关闭

在当前的实现中,server端有个启动参数来控制”是否开启身份认证“。如果需要关闭这一feature,只要重启server就可以了。

一个可能的需求是:通过RPC命令来关闭认证。这样的需求可以实现,但是有些奇怪。原因在于:

  • 对于一个开启了认证的SERVER,任何的RPC命令应该都要求认证
  • 但当需要关闭认证时,说明认证系统已经出问题了。那么这个RPC命令可以说是无法发送到server端的。

所以通过重启的方式来关闭认证是比较合理的,这也使得整个安全过程的边界很清楚:你应该在身份认证层的外部来打破保护本身。从实用的角度来看,当认证系统出问题时,可用性一定会出问题。那么你在恢复可用性时所采取的操作,究竟是重启、还是发起另一个命令,对于整个系统不可用时长的影响,应该是可以忽略不计的。

另一个需求可能是:如果不能继续更新Ticket,就自动降级到不需要认证。这也涉及到了分布式系统的一个基本问题:从进程自己的角度来看,它永远无从得知,不能更新Ticket,是自己不行了,还是网络不行了,还是认证系统不行了。贸然的关闭掉认证系统,相当于直接关掉了整个安全层,是不明智的。所以这个动作最好还是交给管理员进行评估。

最后,如果真的需要为了可用性添加这些功能,还是要多借鉴别的项目在使用Kerberos的经验。

有了认证后,怎么方便的做ACL

在身份认证成功后,rpc session可以得知认证方的用户名。在每收到一条消息后,session会把用户名存入到每一条message中。这样就可以得知每个message的用户名了:

msg->user_name

假如kerberos的用户名是"sample_user/[email protected]",那么user_name即为"sample_user/pegasus",即没有realm的部分。

认证的进一步讨论

对于一个完备的认证过程而言,每条普通的rpc_message都需要携带身份相关的凭据。之所以这么做,是为了防止重放攻击:即一个攻击者在session认证成功后再劫持会话。

但我们在实现上并没有这么做。一方面而言,这么做会导致rpc_message的格式不兼容,给升级带来比较大的困扰;另一方面,身份认证的主要目的是为了防止内网用户的误操作,而非防止公网用户的攻击。

另一个值得注意的是,如果在建立会话时使用的ticket超时了,那么这个会话要不要关闭掉?当前也是从够用的角度出发,没有处理这个问题。

实现

当前的实现在shengofsun/master的branch上

如何配置

[security]
;; keytab文件,密钥要和krb5_principal相对应
krb5_keytab = /home/weijiesun/source/github/pegasus/local-kdc/krb5.keytab
;; kerberos的配置文件,client和server都需要
krb5_config = /home/weijiesun/source/github/pegasus/local-kdc/krb5.conf
;; 进程启动时候自己的principal
krb5_principal = test-server/[email protected]
;; 要访问的server的用户名
service_name = test-server
;; 要访问哦server的fqdn
service_fqdn = myhost
;; sasl的路径
sasl_plugin_path = /home/weijiesun/source/github/pegasus/rdsn/thirdparty/output/lib/sasl2
;; 是否开启认证
open_auth = true
;; 是否强制认证
mandatory_auth = true

测试要点

  1. 可以写个基本的rpc_session建立连接的单元测试,但前提是必须有KDC。可以调研下有没有轻量级的KDC,实在不行,就用我们编译好的KDC搭一个。
  2. 要测试下集群的升级和降级,这个应该只能手动测试。
  3. 要测试下ticket expire,并且本地还没有更新ticket的情况。这个可能需要加个参数来让schedule时间长一些。测试结果是观测到各种报错、新连接建不上、但进程不崩溃。
  4. 要测试一下KDC不可用的情况,把我们搭的KDC关了就行。测试的结果也是观测到各种报错,新连接建立不上。
  5. 写个脚本不停的建立、关闭连接(用shell就可以了),让他跑个一两周,看看内存有没有问题。
@acelyc111
Copy link
Member

Completed by commits in https://github.com/XiaoMi/rdsn prefixed with 'feat(security): ', such as XiaoMi/rdsn#575

cauchy1988 pushed a commit to cauchy1988/incubator-pegasus that referenced this issue May 7, 2022
acelyc111 pushed a commit that referenced this issue Jun 23, 2022
@acelyc111
Copy link
Member

#170

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
component/auth authorization & anthentication
Projects
None yet
Development

No branches or pull requests

4 participants