Apache Thrift - 安全注意事项

使用 Apache Thrift 构建分布式系统时,关注安全性以保护您的数据并确保服务之间的通信安全且私密非常重要。

本教程将介绍关键的安全方面,例如如何验证用户、控制访问、加密数据以及遵循最佳实践以确保一切安全。

身份验证

身份验证可确保与您的 Thrift 服务交互的实体(客户端和服务器)是他们声称的身份。这是确保通信安全并保护敏感数据的关键步骤。

以下是不同类型的身份验证 −

  • 基本身份验证
  • 基于令牌的身份验证
  • 相互 TLS (mTLS)

基本身份验证

基本身份验证要求用户提供用户名和密码才能访问服务。虽然它简单易行,但本身并不十分安全,因为凭证通常以纯文本形式发送。

基于令牌的身份验证

在此方法中,客户端登录后会收到一个令牌,例如 JSON Web 令牌 (JWT)。然后使用此令牌访问服务。

令牌可以包含到期时间和范围,与基本身份验证相比,此方法更安全、更灵活。

相互 TLS (mTLS)

相互 TLS 通过要求客户端和服务器相互出示证书来增强安全性。这种双向身份验证过程可确保双方都得到验证,从而为通信提供高级别的安全性。

实现基于令牌的身份验证

基于令牌的身份验证通过使用令牌(例如 JWT(JSON Web 令牌))来验证用户或系统的身份,从而增强安全性。

使用 JWT 的示例

以下是有关如何在 Thrift − 中实现基于令牌的身份验证的分步指南

生成令牌:您生成一个包含有关用户的信息和到期时间的令牌。此令牌使用密钥签名以防止篡改 −

import jwt
import datetime

def generate_token(secret_key):
payload = {
    'exp': datetime.datetime.utcnow() + datetime.timedelta(hours=1), # Token 在 1 小时后过期
    'iat': datetime.datetime.utcnow(), # 当前时间签发
    'sub': 'user_id' # Token 的主题,例如用户 ID
}
return jwt.encode(payload, secret_key, algorithm='HS256') # 使用 HS256 算法对 token 进行编码

验证请求: 当请求到来时,检查请求标头中提供的 token。如果 token 有效且未过期,则允许该请求;否则,它将被拒绝 −

from thrift.protocol import TBinaryProtocol
from thrift.transport import TTransport
from flask import Flask, request, jsonify

app = Flask(__name__)
secret_key = 'your_secret_key' # 用于编码和解码 token 的密钥

def decode_token(token):
   try:
      payload = jwt.decode(token, secret_key, algorithms=['HS256'])  # 使用密钥解码令牌
      return payload
   except jwt.ExpiredSignatureError:
      return None  # 如果 token 已过期,则返回 None

@app.route('/some_endpoint', methods=['GET'])
def some_endpoint():
   token = request.headers.get('Authorization')  # 从请求标头中获取令牌
   if decode_token(token):
      return jsonify({'message': 'Authenticated'}), 200  # 如果 token 有效,则返回成功消息
   else:
      return jsonify({'message': 'Unauthorized'}), 401  # 如果令牌无效或已过期,则返回错误消息

授权

授权是关于确定用户或服务在经过身份验证后可以执行哪些操作。它确保个人或系统只能根据其角色或属性访问或修改他们被允许访问的资源。

基于角色的访问控制

基于角色的访问控制 (RBAC) 根据用户在组织中的角色为其分配权限。每个角色都有一组与之关联的特定权限,并将用户分配给这些角色。

此方法通过将权限分组到角色并将这些角色分配给用户来简化权限管理。

  • 定义角色和权限:您定义不同的角色(例如,管理员、用户)并指定每个角色可以做什么(例如,读取、写入、删除) −
  • roles_permissions = {
       'admin': ['read', 'write', 'delete'],
       'user': ['read']
    }
    
  • 检查权限:在允许操作之前,检查用户的角色是否具有所需的权限 −
  • def check_permission(role, permission):
       if permission in roles_permissions.get(role, []):
          return True
       return False
    
    @app.route('/delete_resource', methods=['POST'])
    def delete_resource():
       role = get_user_role()  # 假设此函数检索用户的角色
       if check_permission(role, 'delete'):
          # 执行删除操作
          return jsonify({'message': 'Resource deleted'}), 200
       else:
          return jsonify({'message': 'Forbidden'}), 403
    

基于属性的访问控制

基于属性的访问控制 (ABAC) 根据各种属性(例如用户的角色、资源的属性或当前环境条件)授予或限制访问权限。

与 RBAC 相比,此方法通过考虑多种因素提供更精确的控制。

  • 定义属性和策略:建立基于属性(例如用户角色或资源所有者)确定访问权限的规则 −
  • def can_access(user_role, resource_owner):
    return user_role == 'admin' or (user_role == 'user' and resource_owner == 'user')
    
  • 执行策略:在应用程序中实施检查以确保遵循策略 −
  • @app.route('/access_resource', methods=['GET'])
    def access_resource():
       user_role = get_user_role()
       resource_owner = get_resource_owner()
       if can_access(user_role, resource_owner):
          # Access resource
          return jsonify({'message': 'Resource accessed'}), 200
       else:
          return jsonify({'message': 'Forbidden'}), 403
    

加密

加密是保护数据的重要过程,可使未经授权的用户无法读取数据。它既可以在数据通过网络传输时保护数据,也可以在数据存储在磁盘上时保护数据。

传输中的数据加密

传输中的加密可确保在客户端和服务器之间发送的数据不被窃听或篡改。这是通过在数据通过网络传输时对其进行加密来实现的。

使用 TLS 进行安全通信:TLS(传输层安全性)是一种在传输过程中加密数据的协议,可确保客户端和服务器之间的安全通信 −

在 Thrift 服务器上启用 TLS:您需要通过提供服务器的证书和密钥来配置 Thrift 服务器以使用 TLS。此设置会在数据从客户端发送到服务器时对其进行加密 −

from thrift.server import TServer
from thrift.transport import TSSLTransport

handler = MyHandler()
processor = MyService.Processor(handler)

# 设置 TLS
server_transport = TSSLTransport.TSSLServerSocket('localhost', 9090, 'server_cert.pem', 'server_key.pem')
transport_factory = TTransport.TBufferedTransportFactory()
protocol_factory = TBinaryProtocol.TBinaryProtocolFactory()

server = TServer.TSimpleServer(processor, server_transport, transport_factory, protocol_factory)
server.serve()

在 Thrift 客户端上启用 TLS:同样,配置 Thrift 客户端以使用 TLS 来确保从服务器接收的数据是加密且安全的 −

from thrift.transport import TSSLTransport

# 设置 TLS
transport = TSSLTransport.TSSLSocket('localhost', 9090, validate=False, ca_certs='ca_cert.pem')
protocol = TBinaryProtocol.TBinaryProtocol(transport)

静态数据加密

静态加密可保护存储在磁盘上的数据。即使有人物理访问您的存储,加密数据仍然是安全的,没有正确的解密密钥就无法访问。

AES 加密示例:

  • 加密数据:使用高级加密标准 (AES) 在存储数据之前对其进行加密。这涉及使用密钥将数据转换为不可读的格式 −
  • from Crypto.Cipher import AES
    from Crypto.Util.Padding import pad
    
    def encrypt_data(data, key):
       cipher = AES.new(key, AES.MODE_CBC)
       ciphertext = cipher.encrypt(pad(data, AES.block_size))
       return cipher.iv + ciphertext
    

    此处,cipher.iv 是用于加密的初始化向量,ciphertext 是加密数据。

  • 解密数据:要读取加密数据,您需要使用加密期间使用的相同密钥和初始化向量对其进行解密 −
  • from Crypto.Cipher import AES
    from Crypto.Util.Padding import unpad
    
    def decrypt_data(encrypted_data, key):
       iv = encrypted_data[:AES.block_size]
       ciphertext = encrypted_data[AES.block_size:]
       cipher = AES.new(key, AES.MODE_CBC, iv=iv)
       return unpad(cipher.decrypt(ciphertext), AES.block_size)
    

    此函数从加密数据中提取初始化向量,解密密文,并删除加密过程中添加的填充。