前言
某次code review 时,发现一个令人费解的代码实现。code review 的这套系统是基于Restful API微服务架构风格的,如下图。
费解的点在于a、b、c三个微服务应用都是同一个团队开发的,为啥a服务每次Restful API访问 b/c 时都在http header带上一个HTTP Basic Authentication? 我刚开始时以为是终端用户的的用户名、密码,但再一想觉得不对。
分析
HTTP Basic Authentication(HTTP 基本认证)是 HTTP 1.0 提出的一种认证机制。
基本原理是:客户端发送 HTTP Request 给服务器时,把用户名和密码用 BASE64 加密后,放在 Authorization Header 中发送给服务器。服务器将 Authorization Header 中的用户名密码取出,进行验证, 如果验证通过,将根据请求,发送资源给客户端。
这种认证方式已经是非常古老了,基本都不再使用。Base64只是一种编码转换,并不是真的加密。这套系统(microservice application a、b、c)部署于内网,没有使用https只是使用了http。而且细看代码发现,每次访问都会带上这个Basic Authentication Header,密码验证只是简单的对比配置文件所配置的用户名、密码。配置文件中有限的几个用户名、密码显然不可能是终端用户的。那它是干啥用的?
带着这样的疑问,百思不解,只好找到开发这个程序的老员工问问了。据说,是不想microservice-b / microservice-c 被其它微服务应用程序所访问,目前只开放给microservice-a访问。所以通过这种方式控制。这......让我有种被雷击的感觉。一般来说部署于内网(server-farm网络区域)的微服务,是不会直接对外暴露,外来请求只能通过api-gw的认证授权才能进来。而且每一个内网的微服务应用相互间的访问接口也不是说直接点个按钮或者配置一个地址就能访问,一般也是经过需求分析、沟通开发、集成测试、部署上线,微服务应用间才打通访问的。所以内网的微服务相互间一般是可以相互信任的。这个Basic Authentication显得多余了。前面说的是内网的微服务间,不同于终端用户(客户端)的认证鉴权问题,后者可以参考《David Borsos 在伦敦的微服务大会上提出的四种方案》。
但凭这想要说服团队放弃这种Basic Authentication的用法,说服力是不够的。业务需求总是多种多样的,总有各种奇奇怪怪的的需求。遥想7年前,就曾被人问过,假设microservice-c是一个账户记账服务,在整个公司的所有应用系统中都是一个至关重要的微服务应用,毕竟涉及到金钱的来往。那有什么更好的解决方案吗?当年还没有流行K8S,也还没有容器化,是基于VM部署的。当年的解决办法也很简单 ,直接ip-table,在Linux系统上限定网络防火墙设置,只有有限的几个IP地址可以访问记账服务。那现在基于K8S部署,Pod的IP地址是可以漂移的,ip-table不好使啊。虽然也有各种技术可以解决,绑定静态ip , 基于k8s dns ,service vip 等。但这要求对K8S非常熟悉,也比较耗费运维人员的精力。
再来思考上面Basic Authentication 的方案,即使http替换为https也不够安全。以Application作为授权主体,在配置文件中分配固定的用户名密码,由服务提供者分发给服务消费者,而开发人员相互间的沟通是很广泛的。开发microservice-a的同学,在新开发microservice-e时,可能懒得再走一遍沟通的流程了,直接在microservice-e上配上了microservice-a所用的用户名、密码。嗯,也能跑通了。最终形同虚设。
调查
为了验证猜想,是否真的形同虚设。我抽查了N多个应用,查看其配置的用户名、密码及检查Basic Authentication的源代码。发现真的是形同虚设,有些不同的Application使用同一个用户名,密码设置也很随意。而且基本每一个microservice 都有关于Basic Authentication的这段源代码,里面有很多注释都是一样的,很明显的copy paste 痕迹。
解决方案一:保留Basic Authentication
如果仍然保留Basic Authentication的思路,就得在项目管理流程努力。技术上要改两个点:
- http替换为https;
- 用户名、密码保存在配置中心,而不是本地配置文件,例如Apollo这样的独立配置系统加密配置,并且生产环境只授权给运维人员才有权配置。
缺点:很明显,很费运维人员。而且一旦Basic Authentication Header落地日志,也废了。只能说聊胜于无,也能解决小规模应用的问题。
解决方案二:利用服务注册中心授权
内网的微服务应用程序相互间的通信基于TLS/HTTPS双向认证,这要求Microservice Application每一个部署实例都同时部署有一张CA证书。其实Nginx / Tomcat / Jetty 或其它各种Web Server都是直接支持的。只是默认很多是单向认证,这里需要配置为双向认证。为了省钱,可以由运维团队自己颁发证书,不一定需要买商业CA机构的。
Application作为服务提供者:
- 当应用启动时,向服务注册中心双向认证各自的身份,获得服务注册中心的公钥(pub-key)。这个公钥会用于后续接受服务消费者的请求时验签。
- 服务提供者向服务注册中心,登记自己所能提供的服务。
- 当消费者访问时,从其请求头部中获得token,使用pub-key 验签。验签通过后可允许服务消费者访问真正的服务。
Application作为服务消费者:
- 当应用启动时,向服务注册中心双向认证各自的身份。
- 消费者向服务注册中心登记自己将来需要访问的服务。服务注册中心查看授权表,签发允许授权的令牌(token内明确消费者名字、提供者名字、服务接口名字/URL)。缓存令牌。
- 消费者向提供者直接访问服务时,带上注册中心授予的令牌。
服务注册中心作为大统一的管理平台:
- 负责登记所有微服务的提供、消费关系及基于这些关系的授权。以树状记录于etcd/zookeeper。但对Application的认证已经交给证书所负责,可以比较简单的解决。
- 可以展示依赖关系(网状),application name as consumer ---- (micro-service-interface-name)----> application name as provider 。
- 可以报告每一个服务提供者的就绪状态(在线的列表)。
- 可以报告每一个服务消费者的就绪状态(其所需要的服务是否在线),授权状态(获得的令牌列表)。
优点: 支持大规模微服务治理,对于大量微服务应用相互间授权访问的情况,可以统一集中的管理,管理成本低。
缺点: 技术复杂一些。但有些开源的服务注册中心有支持,可以根据公司实际需要,稍微修改即可。