RPC框架设计的安全性考量

时间:2023-02-11 15:59:09

RPC里面该如何提升单机资源的利用率,你要记住的关键点就一个,那就是“异步化”。调用方利用异步化机制实现并行调用多个服务,以缩短整个调用时间;而服务提供方则可以利用异步化把业务逻辑放到自定义线程池里面去执行,以提升单机的OPS。

1 为何要考虑安全问题?

RPC是解决应用间互相通信的框架,而应用之间的远程调用过程一般不会暴露在公网,RPC一般用于内部应用通信。

“内部”指应用都部署在同一大局域网。相对公网环境,局域网隔离性更好,相对安全,所以在RPC里很少考虑数据包篡改、请求伪造等。

那RPC里应关心什么安全问题?

2 完整的RPC应用流程

先由Provider定义好一个接口,并把这个接口的Jar包发布到私服,然后在项目中实现这接口,最后通过RPC API把这接口和其对应实现类完成对外暴露,若是Spring应用,直接定义成一个Bean即可。至此,服务提供方就完成了一个接口的对外发布。

对服务调用方就更简单,只要拿到刚才上传到私服的Jar坐标,就可把发布到私服的Jar引入项目,借助RPC动态代理功能,服务调用方直接就可在项目完成RPC调用。

安全隐患

因为私服上所有Jar坐标所有人都可看到,只要拿到Jar坐标,就可将发布到私服的Jar引入项目,完成RPC调用了吗?公司内部这种不向服务提供方咨询就直接调用的行为很少发生,一般真实业务的接口出入参数都不太简单,这样不经过咨询只靠调用方自己猜测完成调用的工作效率实在太低。

虽然这种靠猜测调用的概率很小,但当调用方在其它新业务场景要用之前项目中使用过的接口,很可能真的不跟服务提供方打招呼就直接调用。这行为对Provider很危险,因为接入新调用方就意味着承担的调用量变大,有时可能新增的调用量会成为Provider的“最后一根稻草”,导致Provider无法正常提供服务,还不知道是被谁给压垮的。

可通过公司严格流程让研发达成“君子约定”,即在应用里每次要用一个接口时,须先向服务提供方报备,这确实能在很大程度避免。但就RPC本身,可提供某种功能来解决这种问题呢?毕竟对人数多的团队,口头约定不能彻底杜绝这类问题。

3 调用方之间的安全保证

根本原因就是服务提供方收到请求后,不知道这次请求是哪个调用方发起的,没法判断这次请求是属于之前打过招呼的调用方还是没有打过招呼的调用方,所以也就没法选择拒绝这次请求还是继续执行。

只需给每个调用方设定一个唯一身份,每个调用方在调用前都先来服务提供方这登记身份,只有登记过的调用方才能继续放行,没有登记过的调用方一律拒绝。

好比坐火车凭身份证购买火车票,买票成功就类似服务调用方去服务提供方进行登记。当你进站准备上火车,须同时出示你的身份证和火车票,这两个就是代表你能上这趟火车的“唯一身份”,只有验证身份,负责检票的工作人员才会让你上车,否则拒乘。

4 RPC里的实现

先要有个可供caller进行调用接口登记的地方,“授权平台”,caller可在授权平台上申请自己应用里要调用的接口,而provider在授权平台进行审批,只有服务提供方审批后调用方才能调用。但这解决调用数据收集的问题,并没有完成真正授权认证功能,缺少一个检票环节。

搭建了授权平台,而且接口的授权数据也在这平台,可将检票环节放到授权平台。caller每次发起业务请求时,先去发一条认证请求到授权平台:“hello,我能调用这个接口吗?”

只有授权平台返回:“sure”,才继续把业务请求发到provider。

4.1 集中式认证流程

RPC框架设计的安全性考量

@startuml
title:集中式认证流程

participant 调用方 as 调用方
participant 授权平台 as 授权平台
participant 提供方 as 提供方

调用方 -> 授权平台++ : 我们调用 x 接口吗?
授权平台 --> 调用方-- : sure!

调用方 -> 提供方++ : 真实业务请求
提供方 --> 调用方-- : 返回结果

@enduml

这种设计没问题,整个认证过程对RPC使用者透明。问题是这授权平台承担公司内所有RPC请求的次数总和,当公司内部RPC使用程度高了,这授权平台就会成为性能瓶颈,须保证高可用。

如何优化呢?是否不需要把这认证逻辑放到业务请求过程,而是挪到初始化过程?确实可很大程度减少授权平台压力,但本质无变化,还是集中式授权平台。

5 更优方案

caller能否调相关接口,由provider说了算,就可将检票过程放到provider。在caller启动初始化接口时,带上授权平台上颁发的身份去provider认证下,当认证通过后,就认为这接口可调。

provider验票时,对照的数据来自哪儿,不能又去请求授权平台吧?

不可逆加密算法

如HMAC。provider应用里放个用于HMAC签名的私钥,在授权平台上用这私钥为申请调用的调用方应用进行签名,这签名生成的串就变成caller唯一身份。

provider收到caller的授权请求后,只需验证这个签名跟调用方应用信息是否对应得上,这样集中式授权瓶颈就不存在了。

HMAC来做认证,服务提供方是使用 “公钥” 验证服务调用方的签名串。事先要生成公私钥。

6 服务发现也有安全问题?

解决了调用方之间的安全认证问题。还有其它安全问题吗?

服务提供方会把接口Jar发布到私服,以方便调用方能引入项目快速完成RPC调用,可能有人拿到Jar后,发布出来一个服务提供方呢?导致调用方通过服务发现拿到的服务提供方IP地址集合里有那个伪造的提供方。

解决这个问题的根本:要把接口跟应用绑定,一个接口只允许有一个应用发布提供者,避免其它应用也能发布该接口。

实现

服务提供方启动时,把接口实例在注册中心登记。

可利用这流程,注册中心在收到服务提供方注册请求时,验证请求过来的应用是否跟接口绑定的应用一样,只有相同才允许注册,否则返回错误信息给启动的应用,避免假冒的服务提供者对外提供错误服务。

7 总结

RPC常用于解决内网应用之间的调用,内网环境相对公网也没有那么恶劣,也要去建立一套可控的安全体系,去防止一些错误行为。

对RPC,我们所关心的安全问题不会有公网应用那么复杂,我们只要保证让服务调用方能拿到真实的服务提供方IP地址集合,且服务提供方可以管控调用自己的应用就够了。

8 FAQ

前面讲的调用方之间的安全问题,我们更多只是解决认证问题,并没有解决权限问题。一个RPC接口定义里面包含多个方法,目前只是解决你能不能调用接口,并没有解决你能调用我接口里面的哪些方法,如何解决?

订单中间件,提供服务防止未知系统任意调用,服务调用管理功能,到方法级:

  • 提供服务调用的申请模板,描述那个系统,那个应用,调用我们那个服务的那个方法,调用量多少,调用应用负责人是谁,对接研发谁,还有别的配置信息
  • 把调用配置信息落库,然后放入缓存,使用调用者信息及我们提供服务的信息组成key,放入redis
  • 调用方调用我们时,去做拦截和鉴权
  • 统一部署平台中有系统和应用的唯一标识信息,这些信息在调用接口时作为入参的一部分,就能实现细粒度到方法基本的控制

另外,就是使用token,也是申请时分发,本质一样的,就看对应的配置信息存储在哪里。

服务端提供的安全校验方式

md5摘要校验

安全级别较低,服务端直接提供或者网关代劳。

非对称加密算法

签名(RSA和HMAC等算法,服务端直接提供或者网关代劳。

Oauth2授权

有单独的授权服务,四种模式。

觉得rpc好像跟k8s有点像,k8s是类似把一个大服务拆分成很多模块,完了之后来把这些微服务管理起来,而rpc是提供了一系列接口,通过网络的方式,让各个调用方去调用。

k8s可以看做rpc升级版本。

可以从鉴权和授权2个方面来看(参考k8s):

  • 鉴权可以用 token 的方式,token鉴权应该是目前用的最多的一种认证方式
  • 授权可以用 RBAC 方式,类似k8s里面的 serviceAccount 和 role 和 roleBinding 等

认证和授权一般都是同时使用。

我们之前是用的配置中心来存储对应的关系,B服务可以被那些服务调用或者不可以被那些服务调用,同时,也有B服务的接口权限,然后内部网关同步相关信息,保存到内存缓存中,当调用方请求经过网管时,获取调用方,服务方,服务方具体的接口,然后去校验相关的信息,判断是否可以放行,我们的鉴权没有放在服务方本身去实现,是在网关实现的。

前提就是怎么识别调用方的身份,比如应用id。

请问服务提供方对服务调用方的授权认证后,已认证的状态应该存在服务提供端本地吧,那服务端集群中各个节点之间怎么共享同一个服务调用方应用的认证状态呢?还是说每次调用到不同的的服务提供方节点都需要从新进行鉴权?或者说是服务调用方在获取到服务提供方的IP列表后统一进行一次遍历的授权认证?

每个节点都得鉴权一次。

RPC可以用于公网通信吗?鉴权放在服务端对服务端的压力是不是也挺大的?

公网一般比较少,鉴权只要一次开销,压力还是可控的。

这种授权方式,对于调用方而言,加密后的签名怎么维护呢,感觉有代码侵入呀?

统一授权平台上获取。

首先肯定是在服务端做,其次是应该在每一个服务提供端对其接口做权限审核,假如一个服务端提供10个接口出来,5个可以被A调用,另外5个不能被A服务调用,那么就应该有个配置文件配置这调用端和服务端各个接口的权限配置信息,这个权限配置应该放在配置中心,然后每个服务端会动态获取配置信息根据配置信息来决定是否拒绝服务。对于新增的调用服务,它需要先申请权限,然后服务端提供方修改配置文件对其提供服务。

这也是一种思路。

权限控制应放在服务提供方,权限控制规则可以放到单独管理节点,服务启动的时候从管理节点获取规则,权限规则变更后下发到服务节点。

这有一个前提就是统一调用方身份的问题,规则里面是按照什么对象来识别身份。

进行接口授权的同时也进行接口方法的授权。

授权和认证都需要考虑。