Skip to content

RabbitMQ SSL/TLS 加密

概述

SSL/TLS(Secure Sockets Layer/Transport Layer Security)是保护网络通信安全的核心技术。RabbitMQ 支持 TLS 加密来保护客户端与服务器之间的数据传输,防止数据在传输过程中被窃听、篡改或伪造。

核心知识点

TLS 加密架构

┌─────────────────────────────────────────────────────────────┐
│                    TLS 加密通信流程                          │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐                      ┌─────────────┐       │
│  │   Client    │                      │  RabbitMQ   │       │
│  │  (生产者/    │                      │   Server    │       │
│  │   消费者)    │                      │             │       │
│  └──────┬──────┘                      └──────┬──────┘       │
│         │                                    │              │
│         │  1. Client Hello                   │              │
│         │───────────────────────────────────▶│              │
│         │                                    │              │
│         │  2. Server Hello + Certificate     │              │
│         │◀───────────────────────────────────│              │
│         │                                    │              │
│         │  3. Key Exchange                   │              │
│         │───────────────────────────────────▶│              │
│         │                                    │              │
│         │  4. Encrypted Data Transfer        │              │
│         │◀──────────────────────────────────▶│              │
│         │                                    │              │
│  └───────────────────────────────────────────┴─────────────┘
│                                                             │
└─────────────────────────────────────────────────────────────┘

TLS 版本支持

TLS 版本支持状态安全性推荐使用
TLS 1.3完全支持极高✅ 强烈推荐
TLS 1.2完全支持✅ 推荐
TLS 1.1已废弃❌ 不推荐
TLS 1.0已废弃❌ 禁止
SSL 3.0已废弃不安全❌ 禁止
SSL 2.0已废弃不安全❌ 禁止

加密套件选择

推荐加密套件(按优先级排序):

1. TLS_AES_256_GCM_SHA384 (TLS 1.3)
2. TLS_CHACHA20_POLY1305_SHA256 (TLS 1.3)
3. TLS_AES_128_GCM_SHA256 (TLS 1.3)
4. ECDHE-RSA-AES256-GCM-SHA384 (TLS 1.2)
5. ECDHE-RSA-AES128-GCM-SHA256 (TLS 1.2)

避免使用的加密套件:
- 包含 RC4、DES、3DES 的套件
- 包含 MD5、SHA1 的套件
- 不支持前向保密的套件

TLS 端口配置

服务默认端口TLS 端口说明
AMQP56725671消息队列协议
Management1567215671管理界面
MQTT18838883MQTT 协议
STOMP6161361614STOMP 协议
Clustering2567225671集群通信

配置示例

基础 TLS 配置

rabbitmq.conf 中配置 TLS:

conf
# AMQP TLS 监听器
listeners.ssl.default = 5671

# TLS 证书配置
ssl_options.cacertfile = /etc/rabbitmq/ssl/ca_certificate.pem
ssl_options.certfile   = /etc/rabbitmq/ssl/server_certificate.pem
ssl_options.keyfile    = /etc/rabbitmq/ssl/server_key.pem

# 验证模式
ssl_options.verify     = verify_peer
ssl_options.fail_if_no_peer_cert = false

# TLS 版本
ssl_options.versions.1 = tlsv1.3
ssl_options.versions.2 = tlsv1.2

# 加密套件
ssl_options.honor_cipher_order = true
ssl_options.honor_ecc_order = true
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = ECDHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.4 = ECDHE-RSA-AES128-GCM-SHA256

管理 UI TLS 配置

conf
# 管理 UI TLS 配置
management.ssl.port = 15671
management.ssl.cacertfile = /etc/rabbitmq/ssl/ca_certificate.pem
management.ssl.certfile   = /etc/rabbitmq/ssl/server_certificate.pem
management.ssl.keyfile    = /etc/rabbitmq/ssl/server_key.pem

# 强制 HTTPS
management.ssl.verify     = verify_peer
management.ssl.fail_if_no_peer_cert = false

高级 TLS 配置

conf
# TLS 会话缓存
ssl_options.session_timeout = 3600

# 启用 TLS 会话票证
ssl_options.session_tickets = true

# 客户端证书深度
ssl_options.depth = 2

# 启用 OCSP 装订
ssl_options.ocsp_stapling = true

# 证书吊销列表
ssl_options.crl_check = peer
ssl_options.crl_file = /etc/rabbitmq/ssl/crl.pem

# 密码套件安全级别
ssl_options.secure_renegotiate = true

同时支持 TLS 和非 TLS 连接

conf
# 同时监听 TLS 和非 TLS 端口
listeners.tcp.default = 5672
listeners.ssl.default = 5671

# 证书配置
ssl_options.cacertfile = /etc/rabbitmq/ssl/ca_certificate.pem
ssl_options.certfile   = /etc/rabbitmq/ssl/server_certificate.pem
ssl_options.keyfile    = /etc/rabbitmq/ssl/server_key.pem

PHP 代码示例

TLS 安全连接

php
<?php

use PhpAmqpLib\Connection\AMQPSSLConnection;

class RabbitMQTLSConnection
{
    private $config;

    public function __construct(array $config)
    {
        $this->config = $config;
    }

    public function createSecureConnection(): AMQPSSLConnection
    {
        $sslOptions = [
            'cafile' => $this->config['ca_cert'],
            'local_cert' => $this->config['client_cert'] ?? null,
            'local_pk' => $this->config['client_key'] ?? null,
            'verify_peer' => true,
            'verify_peer_name' => true,
            'allow_self_signed' => $this->config['allow_self_signed'] ?? false,
            'ciphers' => 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES256-GCM-SHA384'
        ];

        return new AMQPSSLConnection(
            $this->config['host'],
            $this->config['port'],
            $this->config['username'],
            $this->config['password'],
            $this->config['vhost'],
            $sslOptions,
            [
                'connection_timeout' => 10,
                'read_write_timeout' => 30
            ]
        );
    }

    public function testConnection(): array
    {
        try {
            $connection = $this->createSecureConnection();
            $isConnected = $connection->isConnected();
            $connection->close();

            return [
                'success' => true,
                'connected' => $isConnected,
                'message' => 'TLS 连接成功'
            ];
        } catch (Exception $e) {
            return [
                'success' => false,
                'error' => $e->getMessage()
            ];
        }
    }
}

// 使用示例
$tlsConnection = new RabbitMQTLSConnection([
    'host' => 'localhost',
    'port' => 5671,
    'username' => 'admin',
    'password' => 'password',
    'vhost' => '/',
    'ca_cert' => '/path/to/ca_certificate.pem',
    'client_cert' => '/path/to/client_certificate.pem',
    'client_key' => '/path/to/client_key.pem',
    'allow_self_signed' => false
]);

$connection = $tlsConnection->createSecureConnection();
echo "TLS 安全连接已建立\n";

TLS 连接工厂

php
<?php

use PhpAmqpLib\Connection\AMQPSSLConnection;

class TLSConnectionFactory
{
    private static $instances = [];
    private static $defaultOptions = [
        'verify_peer' => true,
        'verify_peer_name' => true,
        'allow_self_signed' => false,
        'ciphers' => 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES256-GCM-SHA384'
    ];

    public static function create(array $config): AMQPSSLConnection
    {
        $key = self::generateKey($config);

        if (isset(self::$instances[$key])) {
            $instance = self::$instances[$key];
            if ($instance->isConnected()) {
                return $instance;
            }
            unset(self::$instances[$key]);
        }

        $sslOptions = self::buildSSLOptions($config);
        $connectionOptions = $config['options'] ?? [];

        $connection = new AMQPSSLConnection(
            $config['host'],
            $config['port'] ?? 5671,
            $config['username'],
            $config['password'],
            $config['vhost'] ?? '/',
            $sslOptions,
            $connectionOptions
        );

        self::$instances[$key] = $connection;

        return $connection;
    }

    private static function buildSSLOptions(array $config): array
    {
        $options = array_merge(self::$defaultOptions, $config['ssl_options'] ?? []);

        $options['cafile'] = $config['ca_cert'];

        if (isset($config['client_cert'])) {
            $options['local_cert'] = $config['client_cert'];
        }

        if (isset($config['client_key'])) {
            $options['local_pk'] = $config['client_key'];
        }

        if (isset($config['passphrase'])) {
            $options['passphrase'] = $config['passphrase'];
        }

        return $options;
    }

    private static function generateKey(array $config): string
    {
        return md5($config['host'] . ':' . $config['port'] . ':' . $config['username']);
    }

    public static function closeAll(): void
    {
        foreach (self::$instances as $connection) {
            try {
                if ($connection->isConnected()) {
                    $connection->close();
                }
            } catch (Exception $e) {
                error_log("关闭 TLS 连接失败: " . $e->getMessage());
            }
        }
        self::$instances = [];
    }

    public static function fromEnvironment(): AMQPSSLConnection
    {
        return self::create([
            'host' => getenv('RABBITMQ_HOST') ?: 'localhost',
            'port' => (int)(getenv('RABBITMQ_TLS_PORT') ?: 5671),
            'username' => getenv('RABBITMQ_USER'),
            'password' => getenv('RABBITMQ_PASSWORD'),
            'vhost' => getenv('RABBITMQ_VHOST') ?: '/',
            'ca_cert' => getenv('RABBITMQ_CA_CERT'),
            'client_cert' => getenv('RABBITMQ_CLIENT_CERT') ?: null,
            'client_key' => getenv('RABBITMQ_CLIENT_KEY') ?: null
        ]);
    }
}

// 使用示例
$connection = TLSConnectionFactory::fromEnvironment();

TLS 证书验证器

php
<?php

class TLSCertificateValidator
{
    private $caCertPath;
    private $serverCertPath;
    private $serverKeyPath;

    public function __construct(string $caCertPath, string $serverCertPath, string $serverKeyPath)
    {
        $this->caCertPath = $caCertPath;
        $this->serverCertPath = $serverCertPath;
        $this->serverKeyPath = $serverKeyPath;
    }

    public function validateAll(): array
    {
        return [
            'ca_cert' => $this->validateCertificate($this->caCertPath, 'CA'),
            'server_cert' => $this->validateCertificate($this->serverCertPath, 'Server'),
            'server_key' => $this->validateKey($this->serverKeyPath),
            'cert_chain' => $this->validateChain(),
            'expiration' => $this->checkExpiration()
        ];
    }

    public function validateCertificate(string $path, string $type): array
    {
        if (!file_exists($path)) {
            return ['valid' => false, 'error' => "证书文件不存在: {$path}"];
        }

        $content = file_get_contents($path);
        $certInfo = openssl_x509_parse($content);

        if ($certInfo === false) {
            return ['valid' => false, 'error' => '无法解析证书'];
        }

        $now = time();
        $isExpired = $certInfo['validTo_time_t'] < $now;
        $notYetValid = $certInfo['validFrom_time_t'] > $now;

        return [
            'valid' => !$isExpired && !$notYetValid,
            'subject' => $certInfo['subject'],
            'issuer' => $certInfo['issuer'],
            'valid_from' => date('Y-m-d H:i:s', $certInfo['validFrom_time_t']),
            'valid_to' => date('Y-m-d H:i:s', $certInfo['validTo_time_t']),
            'is_expired' => $isExpired,
            'days_remaining' => max(0, floor(($certInfo['validTo_time_t'] - $now) / 86400))
        ];
    }

    public function validateKey(string $path): array
    {
        if (!file_exists($path)) {
            return ['valid' => false, 'error' => "密钥文件不存在: {$path}"];
        }

        $content = file_get_contents($path);
        $privateKey = openssl_pkey_get_private($content);

        if ($privateKey === false) {
            return ['valid' => false, 'error' => '无法解析私钥: ' . openssl_error_string()];
        }

        $keyDetails = openssl_pkey_get_details($privateKey);

        return [
            'valid' => true,
            'type' => $this->getKeyTypeName($keyDetails['type']),
            'bits' => $keyDetails['bits']
        ];
    }

    public function validateChain(): array
    {
        $caCert = file_get_contents($this->caCertPath);
        $serverCert = file_get_contents($this->serverCertPath);

        $caResource = openssl_x509_read($caCert);
        $serverResource = openssl_x509_read($serverCert);

        if ($caResource === false || $serverResource === false) {
            return ['valid' => false, 'error' => '无法读取证书'];
        }

        $isValid = openssl_x509_checkpurpose($serverResource, X509_PURPOSE_ANY, [$caCert]);

        return [
            'valid' => $isValid === true,
            'message' => $isValid === true ? '证书链验证通过' : '证书链验证失败'
        ];
    }

    public function checkExpiration(int $warningDays = 30): array
    {
        $certInfo = openssl_x509_parse(file_get_contents($this->serverCertPath));
        $now = time();
        $daysRemaining = floor(($certInfo['validTo_time_t'] - $now) / 86400);

        return [
            'days_remaining' => $daysRemaining,
            'needs_renewal' => $daysRemaining <= $warningDays,
            'warning_threshold' => $warningDays,
            'expires_at' => date('Y-m-d H:i:s', $certInfo['validTo_time_t'])
        ];
    }

    private function getKeyTypeName(int $type): string
    {
        $types = [
            OPENSSL_KEYTYPE_RSA => 'RSA',
            OPENSSL_KEYTYPE_DSA => 'DSA',
            OPENSSL_KEYTYPE_DH => 'DH',
            OPENSSL_KEYTYPE_EC => 'EC'
        ];

        return $types[$type] ?? 'Unknown';
    }

    public function generateReport(): string
    {
        $results = $this->validateAll();

        $report = "# TLS 证书验证报告\n\n";
        $report .= "生成时间: " . date('Y-m-d H:i:s') . "\n\n";

        $report .= "## CA 证书\n";
        $caResult = $results['ca_cert'];
        $report .= "- 状态: " . ($caResult['valid'] ? '✅ 有效' : '❌ 无效') . "\n";
        if (isset($caResult['valid_to'])) {
            $report .= "- 有效期至: {$caResult['valid_to']}\n";
        }

        $report .= "\n## 服务器证书\n";
        $serverResult = $results['server_cert'];
        $report .= "- 状态: " . ($serverResult['valid'] ? '✅ 有效' : '❌ 无效') . "\n";
        if (isset($serverResult['valid_to'])) {
            $report .= "- 有效期至: {$serverResult['valid_to']}\n";
            $report .= "- 剩余天数: {$serverResult['days_remaining']}\n";
        }

        $report .= "\n## 私钥\n";
        $keyResult = $results['server_key'];
        $report .= "- 状态: " . ($keyResult['valid'] ? '✅ 有效' : '❌ 无效') . "\n";
        if (isset($keyResult['type'])) {
            $report .= "- 类型: {$keyResult['type']}\n";
            $report .= "- 位数: {$keyResult['bits']}\n";
        }

        $report .= "\n## 证书链\n";
        $chainResult = $results['cert_chain'];
        $report .= "- 状态: " . ($chainResult['valid'] ? '✅ 有效' : '❌ 无效') . "\n";

        return $report;
    }
}

// 使用示例
$validator = new TLSCertificateValidator(
    '/etc/rabbitmq/ssl/ca_certificate.pem',
    '/etc/rabbitmq/ssl/server_certificate.pem',
    '/etc/rabbitmq/ssl/server_key.pem'
);

$validation = $validator->validateAll();
print_r($validation);

echo $validator->generateReport();

实际应用场景

场景一:生产环境 TLS 配置

php
<?php

class ProductionTLSConfig
{
    public static function createConnection(): AMQPSSLConnection
    {
        $config = [
            'host' => getenv('RABBITMQ_HOST'),
            'port' => 5671,
            'username' => getenv('RABBITMQ_USER'),
            'password' => getenv('RABBITMQ_PASSWORD'),
            'vhost' => getenv('RABBITMQ_VHOST'),
            'ca_cert' => '/etc/ssl/certs/rabbitmq-ca.pem',
            'ssl_options' => [
                'verify_peer' => true,
                'verify_peer_name' => true,
                'allow_self_signed' => false,
                'ciphers' => 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256'
            ]
        ];

        return TLSConnectionFactory::create($config);
    }
}

场景二:TLS 证书自动续期检查

php
<?php

class CertificateRenewalMonitor
{
    private $validator;
    private $warningDays;
    private $alertCallback;

    public function __construct(TLSCertificateValidator $validator, int $warningDays = 30)
    {
        $this->validator = $validator;
        $this->warningDays = $warningDays;
    }

    public function setAlertCallback(callable $callback): void
    {
        $this->alertCallback = $callback;
    }

    public function check(): array
    {
        $expiration = $this->validator->checkExpiration($this->warningDays);

        $result = [
            'checked_at' => date('c'),
            'days_remaining' => $expiration['days_remaining'],
            'needs_renewal' => $expiration['needs_renewal'],
            'expires_at' => $expiration['expires_at'],
            'alert_sent' => false
        ];

        if ($expiration['needs_renewal'] && $this->alertCallback) {
            call_user_func($this->alertCallback, $result);
            $result['alert_sent'] = true;
        }

        return $result;
    }
}

常见问题与解决方案

问题 1:TLS 握手失败

错误信息

SSL handshake failed: certificate verify failed

解决方案

bash
# 检查证书链
openssl verify -CAfile /etc/rabbitmq/ssl/ca_certificate.pem /etc/rabbitmq/ssl/server_certificate.pem

# 检查证书有效期
openssl x509 -in /etc/rabbitmq/ssl/server_certificate.pem -noout -dates

# 检查证书主题
openssl x509 -in /etc/rabbitmq/ssl/server_certificate.pem -noout -subject

问题 2:协议版本不匹配

错误信息

unsupported protocol

解决方案

conf
# 确保服务器和客户端使用相同的 TLS 版本
ssl_options.versions.1 = tlsv1.3
ssl_options.versions.2 = tlsv1.2

问题 3:加密套件不兼容

解决方案

conf
# 配置兼容的加密套件
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = ECDHE-RSA-AES256-GCM-SHA384
ssl_options.ciphers.4 = ECDHE-RSA-AES128-GCM-SHA256

最佳实践建议

1. 证书管理

php
<?php

class CertificateManager
{
    private $certDir;
    private $backupDir;

    public function backupCertificates(): string
    {
        $timestamp = date('Ymd_His');
        $backupPath = $this->backupDir . '/cert_backup_' . $timestamp;

        if (!is_dir($backupPath)) {
            mkdir($backupPath, 0700, true);
        }

        $files = ['ca_certificate.pem', 'server_certificate.pem', 'server_key.pem'];

        foreach ($files as $file) {
            $source = $this->certDir . '/' . $file;
            if (file_exists($source)) {
                copy($source, $backupPath . '/' . $file);
            }
        }

        return $backupPath;
    }

    public function rotateCertificates(array $newCerts): bool
    {
        $this->backupCertificates();

        foreach ($newCerts as $name => $content) {
            $path = $this->certDir . '/' . $name;
            file_put_contents($path, $content);
            chmod($path, 0600);
        }

        return true;
    }
}

2. TLS 配置模板

conf
# production-tls.conf

# 强制 TLS 1.2 及以上
ssl_options.versions.1 = tlsv1.3
ssl_options.versions.2 = tlsv1.2

# 仅使用安全加密套件
ssl_options.ciphers.1 = TLS_AES_256_GCM_SHA384
ssl_options.ciphers.2 = TLS_AES_128_GCM_SHA256
ssl_options.ciphers.3 = ECDHE-RSA-AES256-GCM-SHA384

# 启用前向保密
ssl_options.honor_cipher_order = true

# 验证客户端证书
ssl_options.verify = verify_peer
ssl_options.fail_if_no_peer_cert = false

# 会话配置
ssl_options.session_timeout = 3600

安全注意事项

重要警告

  1. 私钥保护:服务器私钥必须设置严格的文件权限(600),绝不能泄露
  2. 证书有效期:定期检查证书过期时间,提前续期
  3. 禁用旧协议:禁止使用 SSL 3.0、TLS 1.0、TLS 1.1
  4. 使用强加密:仅使用支持前向保密的加密套件
  5. 证书链完整:确保 CA 证书链完整有效

相关链接