当上线gRPC服务到生产环境的时候,首先需要考虑的就是数据的安全,那么如何保证呢,下面以python为例,进行简单介绍。
RPC的认证方式 RPC服务一般在服务内网使用,不过也有存在于外网的情况,不论是哪种RPC服务,走http2.0或是其他基于TCP实现socket的协议,在部署到生产环境的时候还是需要考虑身份认证加密的,以此来保证数据的安全。
基于SSL/TLS的通道加密 通常身份认证机制是通过SSL/TLS对传输通道加密,以防止请求和响应消息中的敏感数据泄露。使用的场景主要有三种:
后端微服务直接开放给端侧,例如手机app、tv、多屏等,没有统一的API Gateway/SLB做安全接入和认证
后端微服务直接开放给DMZ部署的管理或者运维类Portal
后端微服务直接开放给第三方合作伙伴/渠道
除了上述常用的跨网络场景之外,对于一些安全等级要求比较高的业务场景,即便是内网通信,只要跨主机/VM/容器等,都强制要求对传输通道进行加密。在该场景下,即便只存在内网各模块的RPC调用,仍然需要进行加密。 针对敏感数据的单独加密 有些RPC调用并不涉及敏感数据的传输,或者敏感字段占比较低,为了最大程度的提升吞吐量,降低调用时延,通常会采用HTTP/TCP+敏感字段单独加密的方式。既保证了敏感信息的传输安全,同时也降低了采用SSL/TLS加密通道带来的性能损耗,对于JDK原生的SSL类库,这种性能提升尤为明显。
采用该方案主要有两个缺点:
对敏感信息的识别可能存在偏差,容易遗漏或者过度保护,需要解读数据和隐私保护方面的法律法规,而且不同国家对敏感数据的定义也不同,这会为识别带来很多困难。
接口升级时容易遗漏,例如开发新增字段,忘记识别是否未敏感数据。
gRPC认证的的具体流程 对于gRPC,SSL/TLS协议也是基本的身份加密认证方法,SSL/TLS协议采用公钥加密,客户端向服务端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。 SSL/TLS分为单向认证和双向认证,在实际业务中,单向认证使用较多,即客户端认证服务端,服务端不认证客户端,认证流程如下:
客户端向服务端传送客户端SSL协议的版本号,支持的加密算法种类,产生的随机数,以及其他可选信息
服务端返回握手应答,向客户端传送确认SSL协议的版本号、加密算法的种类、随机数以及其他相关信息
服务端向客户端发送自己的公钥
客户端对服务端的证书进行认证,服务端的合法性校验包括:证书是否过期、发行服务器证书的CA是否可靠、发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”、服务器证书上的域名是否和服务器的实际域名相匹配等
客户端随机生成一个用于后面通讯的“对称密码”,然后用服务端的公钥对其加密,将加密后的“预主密码”传给服务端
服务端用自己的私钥解开加密的“预主密码”,然后执行一系列步骤来产生主密码
客户端向服务端发出信息,指明后面的数据通讯将使用主密码为对称密钥,同时通知服务器客户端的握手过程结束
服务端向客户端发出信息,指明后面的数据通讯将使用主密码为对称密钥,同时通知客户端服务端的握手过程结束
SSL的握手部分结束,SSL安全通道简历,客户端和服务端开始使用相同的对称密钥对数据进行加密,然后通过socket进行传输
具体示例 生成证书 1 openssl req -newkey rsa:2048 -nodes -keyout server.key -x509 -days 3650 -out server.crt
在执行生成证书的过程中,需要填入Country Name
、State or Province Name
、Locality Name
、Organization Name
、Organization Unit Name
、Common Name
、Email Address
等等,这些可以按需填入,或者留空也行。
注:其中的Common Name
支持在客户端连接的时候指定连接的名字,可以自己定义之后填上,否则留空的话可能自动获取不到
代码实现 下面模拟一个任务的grpc调用,服务端将流式响应任务的进度信息proto
定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 syntax = "proto3" ; service Task { rpc StartScan(TaskInfoRequest) returns (stream CommonResponse) ; } message TaskInfoRequest { string task_id = 1 ; string data = 2 ; } message CommonResponse { int32 code = 1 ; string msg = 2 ; string progress = 3 ; }
注:执行下述命令生成python的proto序列化协议源代码python -m grpc_tools.protoc --python_out=. --grpc_python_out=. -I. task.proto
服务端代码server.py
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 import randomimport timefrom concurrent import futuresimport grpcfrom proto import task_pb2, task_pb2_grpcclass TaskServicer (task_pb2_grpc.TaskServicer): def StartScan (self, request, context ): progress = 0 while progress < 100 : resp = { "code" : 200 , "msg" : "succeed" , "progress" : str (progress) } feature = task_pb2.CommonResponse(**resp) tmp = random.randint(1 , 10 ) progress += tmp time.sleep(5 ) yield feature def serve (): server = grpc.server(futures.ThreadPoolExecutor(max_workers=10 ), options=[ ]) task_pb2_grpc.add_TaskServicer_to_server(TaskServicer(), server) with open ("./server.key" , "rb" ) as f: private_key = f.read() with open ("./server.crt" , "rb" ) as f: certificate_chain = f.read() server_credentials = grpc.ssl_server_credentials(((private_key, certificate_chain), )) server.add_secure_port("[::]:50051" , server_credentials) server.start() print ("gRPC服务端已开启,端口为50051..." ) server.wait_for_termination() if __name__ == '__main__' : serve()
客户端代码client.py
如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 import grpcfrom proto import task_pb2, task_pb2_grpcdef get_progress (*host ): with open ("server.crt" , "rb" ) as f: trusted_certs = f.read() credentials = grpc.ssl_channel_credentials(root_certificates=trusted_certs) with grpc.secure_channel("localhost:50001" , credentials) as channel: stub = task_pb2_grpc.TaskStub(channel) req = { "task_id" : "24dsad" , "data" : "12" } features = stub.StartScan(task_pb2.TaskInfoRequest(**req)) for feature in features: print (feature.progress) def run (): with grpc.insecure_channel("localhost:50001" ) as channel: stub = task_pb2_grpc.TaskStub(channel) req = { "task_id" : "1" , "data" : "my test" } features = stub.StartScan(task_pb2.TaskInfoRequest(**req)) for feature in features: print (feature.progress) if __name__ == '__main__' : run()
按照上述实现,即完成了gRPC的认证加密。
抓包分析 运行上述的代码,进行抓包如下,确认gRPC通信已通过SSL/TLS加密认证。