序言web
安全需求算法
安全方案后端
敏感数据加密传输浏览器
认证缓存
鉴权安全
数据完整性和一致性服务器
证书的基本原理微信
单向证书网络
双向证书运维
gRPC安全机制
SSL/TLS认证
GoogleOAuth2.0
自定义安全认证策略
序言
网络安全领域在攻和防对抗规模群体已经成熟,可是两端从业者对于安全原理掌握程度良莠不齐,中间鸿沟般的差距构成了漏洞研究领域的主战场。笔者“三省吾身”,在工做中会犯错误把一些加密、认证、鉴权的概念和实现方案搞混,尤为是加解密涉及算法和公私钥机制的概念不深刻细节。
最近的几个影响颇大的安全漏洞,Apache Shiro 权限绕过漏洞、CVE-2020-14882weblogic 绕过登陆、微软ZeroLogon,这些漏洞原理的共同点都是和基本的安全算法、认证鉴权方案缺陷有关。也许将来的漏洞攻防将转移到安全基础领域的对抗,从业人员除了要求推动安全方案的必要性,涉及安全建设的可用性更为重要,因此特此开专栏系列,为你们普及一些安全基本功。
本文主要经过介绍gRPC的双向认证方案,理清证书领域的知识。
安全需求
RPC是一种技术思想,实现有阿里的 Dubbo/SOFA、Google gRPC、Facebook 的 Thrift,实现时的远程通讯规范和协议能够用RMI、Socket、SOAP(HTTP XML)、REST(HTTP JSON)。这种服务间通讯机制为企业内部各系统、模块之间的微服务和接口之间互相调用,RPC实现须要考虑安全性,RPC 调用安全主要涉及以下三点:
-
我的 / 企业敏感数据加密:例如针对我的的帐号、密码、手机号等敏感信息进行加密传输,打印接口日志时须要作数据模糊化处理等,不能明文打印;
-
对调用方的身份认证:调用来源是否合法,是否有访问某个资源的权限,防止越权访问;
-
数据防篡改和完整性:经过对请求参数、消息头和消息体作签名,防止请求消息在传输过程当中被非法篡改。
安全方案
常见的安全攻防重视rpc协议的反序列漏洞,可是若是业务方问道若是作以上的安全需求,SDL同窗就傻眼了,正确的作法是区分加密传输、认证、鉴权、数据完整性和一致性四个方向:
敏感数据加密传输
基于SSL/TLS的通道加密
当存在跨网络边界的 RPC 调用时,每每须要经过 TLS/SSL 对传输通道进行加密,以防止请求和响应消息中的敏感数据泄漏。跨网络边界调用场景主要有三种:
-
后端微服务直接开放给端侧,例如手机 App、TV、多屏等,没有统一的 API 网关/SLB 作安全接入和认证; -
后端微服务直接开放给 DMZ 部署的管理或者运维类 Portal; -
后端微服务直接开放给第三方合做伙伴 / 渠道。
除了跨网络以外,对于一些安全等级要求比较高的业务场景,即使是内网通讯,只要跨主机 /VM/ 容器通讯,都强制要求对传输通道进行加密。在该场景下,即使只存在内网各模块的 RPC 调用,仍然须要作 SSL/TLS。
使用 SSL/TLS 的典型场景以下所示:
通道加密的的实现技术难度稍大,对性能有损耗,定制化程度高,可是效果显著,建设收益明显
针对敏感数据的单独加密
有些 RPC 调用并不涉及敏感数据的传输,或者敏感字段占比较低,为了最大程度的提高吞吐量,下降调用时延,一般会采用 HTTP/TCP + 敏感字段单独加密的方式,既保障了敏感信息的传输安全,同时也下降了采用 SSL/TLS 加密通道带来的性能损耗,对于 JDK 原生的 SSL 类库,这种性能提高尤为明显。
它的工做原理以下所示:

一般使用 Handler 拦截机制,对请求和响应消息进行统一拦截,根据注解或者加解密标识对敏感字段进行加解密,这样能够避免侵入业务。
采用该方案的缺点主要有两个:
-
对敏感信息的识别可能存在误差,容易遗漏或者过分保护,须要解读数据和隐私保护方面的法律法规,并且不一样国家对敏感数据的定义也不一样,这会为识别带来不少困难; -
接口升级时容易遗漏,例如开发新增字段,忘记识别是否为敏感数据。
认证
内部 RPC 调用的身份认证场景,主要有以下两大类:
-
防止对方知道服务提供者的地址以后,绕过注册中心 / 服务路由策略直接访问 RPC 服务提供端; -
RPC 服务只想供内部模块调用,不想开放给其它业务系统使用(双方网络是互通的)。
身份认证的方式较多,例如 HTTP Basic Authentication、OAuth2 等,比较简单使用的是令牌认证(Token)机制,它的工做原理以下所示:
工做原理以下:
-
RPC 客户端和服务端经过 HTTPS 与注册中心链接,作双向认证,以保证客户端和服务端与注册中心之间的安全; -
服务端生成 Token 并注册到注册中心,由注册中心下发给订阅者。经过订阅 / 发布机制,向 RPC 客户端作 Token 受权; -
服务端开启身份认证,对 RPC 调用进行 Token 校验,认证经过以后才容许调用后端服务接口。
鉴权
身份认证能够防止非法调用,若是须要对调用方进行更细粒度的权限管控,则须要作对 RPC 调用作鉴权。例如管理员能够查看、修改和删除某个后台资源,而普通用户只能查看资源,不能对资源作管理操做。
在 RPC 调用领域比较流行的是基于 OAuth2.0 的权限认证机制,它的工做原理以下:
OAuth2.0 的认证流程以下:
-
客户端向资源拥有者申请受权(例如携带用户名 + 密码等证实身份信息的凭证); -
资源拥有者对客户端身份进行校验,经过以后赞成受权; -
客户端使用步骤 2 的受权凭证,向认证服务器申请资源访问令牌(access token); -
认证服务器对受权凭证进行合法性校验,经过以后,颁发 access token; -
客户端携带 access token(一般在 HTTP Header 中)访问后端资源,例如发起 RPC 调用; -
服务端对 access token 合法性进行校验(是否合法、是否过时等),同时对 token 进行解析,获取客户端的身份信息以及对应的资源访问权限列表,实现对资源访问权限的细粒度管控; -
access token 校验经过,返回资源信息给客户端。
步骤 2 的用户受权,有四种方式:
-
受权码模式(authorization code) -
简化模式(implicit) -
密码模式(resource owner password credentials) -
客户端模式(client credentials)
须要指出的是,OAuth 2.0 是一个规范,不一样厂商即使遵循该规范,实现也可能会存在细微的差别。大部分厂商在采用 OAuth 2.0 的基础之上,每每会衍生出本身特有的 OAuth 2.0 实现。
对于 access token,为了提高性能,RPC 服务端每每会缓存,不须要每次调用都与 AS 服务器作交互。同时,access token 是有过时时间的,根据业务的差别,过时时间也会不一样。客户端在 token 过时以前,须要刷新 Token,或者申请一个新的 Token。
考虑到 access token 的安全,一般选择 SSL/TLS 加密传输,或者对 access token 单独作加密,防止 access token 泄漏。
关于oauth做为安全基本功系列从此还会有专栏。
数据完整性和一致性
RPC 调用,除了数据的机密性和有效性以外,还有数据的完整性和一致性须要保证,即如何保证接收方收到的数据与发送方发出的数据是彻底相同的。
利用消息摘要能够保障数据的完整性和一致性,它的特色以下:
-
单向 Hash 算法,从明文到密文的不可逆过程,即只能加密而不能解密; -
不管消息大小,通过消息摘要算法加密以后获得的密文长度都是固定的; -
输入相同,则输出必定相同。
目前经常使用的消息摘要算法是 SHA-一、MD5 和 hmac,MD5 可产生一个 128 位的散列值。SHA-1 则是以 MD5 为原型设计的安全散列算法,可产生一个 160 位的散列值,安全性更高一些。hmac 除了可以保证消息的完整性,还可以保证来源的真实性。
因为 MD5 已被发现有许多漏洞,在实际应用中更多使用 SHA 和 hmac,并且每每会把数字签名和消息摘要混合起来使用。微信支付、阿里云调用是你们经常使用的签名机制,注意消息摘要不是加密,不是加密,不是加密。
证书的基本原理
目前使用最广的 SSL/TLS 工具 / 类库就是 OpenSSL,它是为网络通讯提供安全及数据完整性的一种安全协议,囊括了主要的密码算法、经常使用的密钥和证书封装管理功能以及 SSL 协议。注意SSL和TLS有不一样的历史和标准,HTTPS的意思是HTTP +SSL/ TLS,如今的安全方案通常是tls实现,SSL标准正被淘汰。只是由于沿袭历史称呼,因此常常混用两次名词, SSL被发现存在过 POODLE, DROWN协议算法自己的漏洞,注意区分大名鼎鼎的心脏滴血漏洞Heartbleed是OpenSSL的实现TLS和DTLS的心跳处理逻辑时有bug,而不是利用SSL/TLS协议自己的缺陷。
单向证书
https是你们最熟悉的单项证书方案,由浏览器、ca中心、服务端三方实现。单向认证的过程,客户端从服务器端下载服务器端公钥证书进行验证,而后创建安全通讯通道。单向认证流程中,服务器端保存着公钥证书和私钥两个文件,整个握手过程以下:

-
客户端发起创建HTTPS链接请求,将SSL协议版本的信息发送给服务器端; -
服务器端将本机的公钥证书(server.crt)发送给客户端; -
客户端读取公钥证书(server.crt),取出了服务端公钥; -
客户端生成一个随机数(密钥R),用刚才获得的服务器公钥去加密这个随机数造成密文,发送给服务端; -
服务端用本身的私钥(server.key)去解密这个密文,获得了密钥R -
服务端和客户端在后续通信过程当中就使用这个密钥R进行通讯了。
双向证书
双向通讯流程,客户端除了须要从服务器端下载服务器的公钥证书进行验证外,还须要把客户端的公钥证书上传到服务器端给服务器端进行验证,等双方都认证经过了,才开始创建安全通讯通道进行数据传输。

-
客户端发起创建HTTPS链接请求,将SSL协议版本的信息发送给服务端; -
服务器端将本机的公钥证书(server.crt)发送给客户端; -
客户端读取公钥证书(server.crt),取出了服务端公钥; -
客户端将客户端公钥证书(client.crt)发送给服务器端; -
服务器端解密客户端公钥证书,拿到客户端公钥; -
客户端发送本身支持的加密方案给服务器端; -
服务器端根据本身和客户端的能力,选择一个双方都能接受的加密方案,使用客户端的公钥加密后发送给客户端; -
客户端使用本身的私钥解密加密方案,生成一个随机数R,使用服务器公钥加密后传给服务器端; -
服务端用本身的私钥去解密这个密文,获得了密钥R -
服务端和客户端在后续通信过程当中就使用这个密钥R进行通讯了。
整个双向认证的流程跑通,最终须要五个证书文件:
-
服务器端公钥证书:server.crt -
服务器端私钥文件:server.key -
客户端公钥证书:client.crt -
客户端私钥文件:client.key -
客户端集成证书(包括公钥和私钥,用于浏览器访问场景):client.p12
生成这一些列证书以前,咱们须要先生成一个CA根证书,而后由这个CA根证书颁发服务器公钥证书和客户端公钥证书。

证书实现的核心是加密,可是也能够被用来作认证,好比istio实现展现了如何用双向证书解决身份、通信安全,:服务器身份(Server identities)被编码在证书里,但服务名称(service names)经过服务发现或 DNS 被检索。安全命名信息将服务器身份映射到服务名称。身份 A 到服务名称 B 的映射表示“受权 A 运行服务 B“。在双向 TLS 握手期间,客户端Envoy作了安全命名检查,以验证服务器证书中显示的服务账户是否被受权运行目标服务。
gRPC安全机制
谷歌提供了可扩展的安全认证机制,以知足不一样业务场景需求,它提供的受权机制主要有四类:
-
通道凭证:默认提供了基于 HTTP/2 的 TLS,对客户端和服务端交换的全部数据进行加密传输; -
调用凭证:被附加在每次 RPC 调用上,经过 Credentials 将认证信息附加到消息头中,由服务端作受权认证; -
组合凭证:将一个频道凭证和一个调用凭证关联起来建立一个新的频道凭证,在这个频道上的每次调用会发送组合的调用凭证来做为受权数据,最典型的场景就是使用 HTTP S 来传输 Access Token; -
Google 的 OAuth 2.0:gRPC 内置的谷歌的 OAuth 2.0 认证机制,经过 gRPC 访问 Google API 时,使用 Service Accounts 密钥做为凭证获取受权令牌。
SSL/TLS认证
用go语言显示下服务端和客户端的调用过程:
服务端使用了证书文件
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
// create the TLS credentials from files
creds, err := credentials.NewServerTLSFromFile("../cert/server.crt", "../cert/server.key")
if err != nil {
log.Fatalf("could not load TLS keys: %s", err)
}
// create a gRPC option array with the credentials
opts := []grpc.ServerOption{grpc.Creds(creds)}
// create a gRPC server object with server options(opts)
s := grpc.NewServer(opts...)
pb.RegisterSimpleMathServer(s, &rpcimpl.SimpleMathServer{})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
客户端使用
func GreatCommonDivisor(first, second string) {
// create the client TLS credentials
creds, err := credentials.NewClientTLSFromFile("../cert/server.crt", "")
// initiate a connection with the server using creds
conn, err := grpc.Dial(address, grpc.WithTransportCredentials(creds))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewSimpleMathClient(conn)
a, _ := strconv.ParseInt(first, 10, 32)
b, _ := strconv.ParseInt(second, 10, 32)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.GreatCommonDivisor(ctx, &pb.GCDRequest{First: int32(a), Second: int32(b)})
if err != nil {
log.Fatalf("cound not compute: %v", err)
}
log.Printf("The Greatest Common Divisor of %d and %d is %d", a, b, r.Result)
}
GoogleOAuth2.0
gRPC 默认提供了多种 OAuth 2.0 认证机制,假如 gRPC 应用运行在 GCE 里,能够经过服务帐号的密钥生成 Token 用于 RPC 调用的鉴权,密钥能够从环境变量 GOOGLE_APPLICATION_CREDENTIALS 对应的文件里加载。若是使用 GCE,能够在虚拟机设置的时候为其配置一个默认的服务帐号,运行时能够与认证系统交互并为 Channel 生成 RPC 调用时的 access Token。
自定义安全认证策略
参考 Google 内置的 Credentials 实现类,实现自定义的 Credentials,能够扩展 gRPC 的鉴权策略。
本文分享自微信公众号 - 安全乐观主义(gh_d6239d0bb816)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。