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 expires in 1 hour
      'iat': datetime.datetime.utcnow(),  # Issued at current time
      'sub': 'user_id'  # Subject of the token, e.g., user ID
   }
   return jwt.encode(payload, secret_key, algorithm='HS256')  # Encode the token with HS256 algorithm

身份验证请求:当请求到达时,您检查请求标头中提供的令牌。如果令牌有效且未过期,则允许请求;否则,请求将被拒绝:

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

app = Flask(__name__)
secret_key = 'your_secret_key'  # Secret key used for encoding and decoding tokens

def decode_token(token):
   try:
      payload = jwt.decode(token, secret_key, algorithms=['HS256'])  # Decode token using the secret key
      return payload
   except jwt.ExpiredSignatureError:
      return None  # Return None if the token has expired

@app.route('/some_endpoint', methods=['GET'])
def some_endpoint():
   token = request.headers.get('Authorization')  # Get the token from request headers
   if decode_token(token):
      return jsonify({'message': 'Authenticated'}), 200  # Return success message if token is valid
   else:
      return jsonify({'message': 'Unauthorized'}), 401  # Return error message if token is invalid or expired

授权

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

基于角色的访问控制

基于角色的访问控制 (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()  # Assume this function retrieves the user's role
       if check_permission(role, 'delete'):
          # Perform delete operation
          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)

# Setup 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

# Setup 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)
    

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

广告