强曰为道
与天地相似,故不违。知周乎万物,而道济天下,故不过。旁行而不流,乐天知命,故不忧.
文档目录

dqlite 分布式 SQLite 教程 / 第 8 章:安全配置

第 8 章:安全配置

本章介绍 dqlite 的安全机制,包括 TLS 加密通信、认证配置、访问控制和安全最佳实践。


8.1 安全威胁模型

dqlite 面临的主要安全威胁:

威胁类型 风险等级 说明
网络窃听 节点间和客户端-节点通信可被监听
中间人攻击 攻击者可篡改通信内容
未授权访问 任何人都可连接并操作数据库
数据泄露 数据目录文件可被直接读取
拒绝服务 恶意连接可耗尽资源
威胁示意图:

  攻击者 ◈──────────────窃听──────────────◈ Node 1
    │                                         │
    │         ┌──────────────┐                │
    └─────────│ 中间人代理   │────────────────│
              └──────┬───────┘                │
                     │                        │
  Client ◈───────────┘──────────────────────◈ Node 2
                     │
                     └──▶ 篡改数据 / 窃取凭证

8.2 TLS 加密通信

8.2.1 TLS 概述

dqlite 支持 TLS(Transport Layer Security)加密所有通信:

通信路径 TLS 保护 说明
节点 ↔ 节点 ✅ 支持 Raft 复制通信
客户端 ↔ 节点 ✅ 支持 SQL 操作通信

8.2.2 生成 TLS 证书

#!/bin/bash
# generate-certs.sh - 生成 dqlite 集群 TLS 证书

CERT_DIR="/etc/dqlite/certs"
mkdir -p "$CERT_DIR"

# 生成 CA 证书
openssl genrsa -out "$CERT_DIR/ca.key" 4096
openssl req -new -x509 -key "$CERT_DIR/ca.key" \
    -out "$CERT_DIR/ca.crt" \
    -days 3650 \
    -subj "/C=CN/ST=Beijing/O=MyOrg/CN=dqlite-ca"

# 为每个节点生成证书
for NODE_ID in 1 2 3; do
    NODE_DIR="$CERT_DIR/node${NODE_ID}"
    mkdir -p "$NODE_DIR"

    # 生成私钥
    openssl genrsa -out "$NODE_DIR/key.pem" 2048

    # 生成证书签名请求(CSR)
    openssl req -new -key "$NODE_DIR/key.pem" \
        -out "$NODE_DIR/csr.pem" \
        -subj "/C=CN/ST=Beijing/O=MyOrg/CN=dqlite-node${NODE_ID}"

    # 配置 SAN(Subject Alternative Name)
    cat > "$NODE_DIR/ext.cnf" <<EOF
[v3_req]
subjectAltName = @alt_names

[alt_names]
DNS.1 = dqlite-node${NODE_ID}
DNS.2 = localhost
IP.1 = 127.0.0.1
IP.2 = 192.168.1.10${NODE_ID}
EOF

    # 使用 CA 签发证书
    openssl x509 -req -in "$NODE_DIR/csr.pem" \
        -CA "$CERT_DIR/ca.crt" \
        -CAkey "$CERT_DIR/ca.key" \
        -CAcreateserial \
        -out "$NODE_DIR/cert.pem" \
        -days 365 \
        -extfile "$NODE_DIR/ext.cnf" \
        -extensions v3_req

    # 验证证书
    openssl x509 -in "$NODE_DIR/cert.pem" -text -noout | grep -E "(Subject:|DNS:|IP:)"

    echo "Node ${NODE_ID} certificates generated in ${NODE_DIR}"
done

# 设置权限
chmod 600 "$CERT_DIR"/*/key.pem
chmod 644 "$CERT_DIR"/*/cert.pem "$CERT_DIR/ca.crt"

echo "All certificates generated in $CERT_DIR"

8.2.3 C API 配置 TLS

/* tls_node.c - 启用 TLS 的 dqlite 节点 */
#include <dqlite.h>
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    dqlite_node *node;
    int rc;

    uint64_t id = 1;
    const char *address = "127.0.0.1:9001";
    const char *data_dir = "/var/lib/dqlite/node1";
    const char *cert_file = "/etc/dqlite/certs/node1/cert.pem";
    const char *key_file = "/etc/dqlite/certs/node1/key.pem";
    const char *ca_file = "/etc/dqlite/certs/ca.crt";

    /* 创建节点 */
    rc = dqlite_node_create(id, data_dir, address, &node);
    if (rc != 0) {
        fprintf(stderr, "Failed to create node\n");
        return EXIT_FAILURE;
    }

    /* 配置 TLS */
    /* 注意:以下 API 为示意,具体函数签名请参考 dqlite.h */
    /*
    rc = dqlite_node_set_tls(node, cert_file, key_file, ca_file);
    if (rc != 0) {
        fprintf(stderr, "Failed to configure TLS: %s\n", dqlite_node_errmsg(node));
        dqlite_node_destroy(node);
        return EXIT_FAILURE;
    }
    */

    /* 启动节点 */
    rc = dqlite_node_start(node);
    if (rc != 0) {
        fprintf(stderr, "Failed to start node: %s\n", dqlite_node_errmsg(node));
        dqlite_node_destroy(node);
        return EXIT_FAILURE;
    }

    printf("TLS-enabled dqlite node started on %s\n", address);

    /* 事件循环 */
    getchar();

    dqlite_node_stop(node);
    dqlite_node_destroy(node);
    return EXIT_SUCCESS;
}

8.2.4 Go 配置 TLS

package main

import (
    "crypto/tls"
    "crypto/x509"
    "fmt"
    "log"
    "os"

    dqlite "github.com/canonical/go-dqlite/v2"
    "github.com/canonical/go-dqlite/v2/driver"
)

func createTLSConfig(certFile, keyFile, caFile string) (*tls.Config, error) {
    // 加载节点证书
    cert, err := tls.LoadX509KeyPair(certFile, keyFile)
    if err != nil {
        return nil, fmt.Errorf("load cert: %w", err)
    }

    // 加载 CA 证书
    caCert, err := os.ReadFile(caFile)
    if err != nil {
        return nil, fmt.Errorf("read CA: %w", err)
    }

    caCertPool := x509.NewCertPool()
    if !caCertPool.AppendCertsFromPEM(caCert) {
        return nil, fmt.Errorf("parse CA cert failed")
    }

    return &tls.Config{
        Certificates: []tls.Certificate{cert},
        RootCAs:      caCertPool,
        ClientCAs:    caCertPool,
        ClientAuth:   tls.RequireAndVerifyClientCert,
        MinVersion:   tls.VersionTLS12,
    }, nil
}

func main() {
    tlsConfig, err := createTLSConfig(
        "/etc/dqlite/certs/node1/cert.pem",
        "/etc/dqlite/certs/node1/key.pem",
        "/etc/dqlite/certs/ca.crt",
    )
    if err != nil {
        log.Fatalf("TLS config failed: %v", err)
    }

    // 创建带 TLS 的 dqlite 节点
    node, err := dqlite.New(1, "127.0.0.1:9001", "/var/lib/dqlite/node1", nil,
        dqlite.WithTLS(tlsConfig),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer node.Close()

    // 创建带 TLS 的驱动
    nodeStore := driver.NewInmemNodeStore()
    nodeStore.Set(context.Background(), []driver.NodeInfo{
        {ID: 1, Address: "127.0.0.1:9001"},
        {ID: 2, Address: "127.0.0.1:9002"},
        {ID: 3, Address: "127.0.0.1:9003"},
    })

    drv, err := driver.New(nodeStore,
        driver.WithTLS(tlsConfig),
    )
    if err != nil {
        log.Fatal(err)
    }
    defer drv.Close()

    fmt.Println("TLS-enabled dqlite cluster started")
}

8.2.5 TLS 配置参数

参数 说明 推荐值
最低 TLS 版本 协议版本 TLS 1.2
密码套件 加密算法 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
客户端认证 双向 TLS RequireAndVerifyClientCert
证书轮换 证书有效期 90 天(自动化轮换)

8.3 认证机制

8.3.1 认证方式对比

方式 安全性 复杂度 说明
无认证 最低 开发测试用
TLS 客户端证书 推荐生产使用
共享密钥 简单场景

8.3.2 TLS 双向认证(Mutual TLS)

双向 TLS 握手流程:

  Client                              Server (dqlite node)
    │                                      │
    │──── ClientHello ─────────────────── ▶│
    │                                      │
    │◀─── ServerHello + ServerCert ────────│
    │     + CertificateRequest              │
    │                                      │
    │──── ClientCert + ClientKeyExchange ─ ▶│
    │     + CertificateVerify               │
    │                                      │
    │     [Server 验证 Client 证书]         │
    │     [Client 验证 Server 证书]         │
    │                                      │
    │◀─── Finished ───────────────────────│
    │                                      │
    │     加密通道建立完成                    │

8.3.3 证书验证策略

// 严格证书验证
tlsConfig := &tls.Config{
    Certificates: []tls.Certificate{cert},
    RootCAs:      caCertPool,
    ClientCAs:    caCertPool,
    ClientAuth:   tls.RequireAndVerifyClientCert,
    MinVersion:   tls.VersionTLS12,

    // 自定义验证逻辑
    VerifyPeerCertificate: func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
        for _, chain := range verifiedChains {
            leaf := chain[0]

            // 检查证书是否过期
            if leaf.NotAfter.Before(time.Now()) {
                return fmt.Errorf("certificate expired: %s", leaf.Subject.CommonName)
            }

            // 检查证书用途
            if leaf.KeyUsage&x509.KeyUsageDigitalSignature == 0 {
                return fmt.Errorf("certificate not valid for digital signature")
            }

            // 检查节点标识(可选)
            // 可以从 CN 或 SAN 中提取节点 ID 并验证
        }
        return nil
    },
}

8.4 访问控制

8.4.1 网络层访问控制

# 使用 iptables 限制 dqlite 端口访问

# 只允许集群节点之间通信
iptables -A INPUT -p tcp --dport 9001 -s 192.168.1.101 -j ACCEPT
iptables -A INPUT -p tcp --dport 9001 -s 192.168.1.102 -j ACCEPT
iptables -A INPUT -p tcp --dport 9001 -s 192.168.1.103 -j ACCEPT
iptables -A INPUT -p tcp --dport 9001 -j DROP

# 只允许应用服务器访问
iptables -A INPUT -p tcp --dport 9001 -s 10.0.0.0/24 -j ACCEPT

8.4.2 systemd 服务安全

# /etc/systemd/system/dqlite.service
[Unit]
Description=dqlite Node
After=network-online.target

[Service]
Type=simple
User=dqlite
Group=dqlite
ExecStart=/usr/local/bin/dqlite-node

# 文件系统安全
ProtectSystem=strict
ReadWritePaths=/var/lib/dqlite
PrivateTmp=true
ProtectHome=true

# 网络安全
RestrictAddressFamilies=AF_INET AF_INET6 AF_UNIX

# 权限限制
NoNewPrivileges=true
ProtectKernelTunables=true
ProtectKernelModules=true
ProtectControlGroups=true

# 资源限制
LimitNOFILE=65536
MemoryMax=512M
CPUQuota=80%

[Install]
WantedBy=multi-user.target

8.4.3 文件系统权限

# 创建专用用户
sudo useradd -r -s /bin/false -d /var/lib/dqlite dqlite

# 设置数据目录权限
sudo mkdir -p /var/lib/dqlite
sudo chown dqlite:dqlite /var/lib/dqlite
sudo chmod 750 /var/lib/dqlite

# 设置证书权限
sudo chown -R dqlite:dqlite /etc/dqlite/certs
sudo chmod 600 /etc/dqlite/certs/*/key.pem
sudo chmod 644 /etc/dqlite/certs/*/cert.pem

8.5 安全最佳实践

8.5.1 安全检查清单

项目 检查内容 状态
TLS 加密 所有通信使用 TLS 1.2+
双向认证 节点间和客户端使用 mTLS
证书管理 自动化证书轮换
网络隔离 防火墙限制端口访问
用户隔离 使用专用系统用户运行
文件权限 数据目录和证书权限正确
日志审计 记录关键操作日志
定期备份 备份策略已实施
版本更新 使用最新稳定版本
监控告警 异常行为告警

8.5.2 证书轮换自动化

#!/bin/bash
# cert-renew.sh - 自动证书轮换脚本

set -euo pipefail

CERT_DIR="/etc/dqlite/certs"
DAYS_BEFORE_EXPIRY=30

check_and_renew() {
    local cert_file="$1"
    local key_file="$2"
    local ca_file="$3"

    # 检查证书是否即将过期
    local expiry_date
    expiry_date=$(openssl x509 -in "$cert_file" -noout -enddate | cut -d= -f2)
    local expiry_epoch
    expiry_epoch=$(date -d "$expiry_date" +%s)
    local now_epoch
    now_epoch=$(date +%s)
    local days_left=$(( (expiry_epoch - now_epoch) / 86400 ))

    if [ "$days_left" -le "$DAYS_BEFORE_EXPIRY" ]; then
        echo "Certificate expires in $days_left days, renewing..."

        # 备份旧证书
        cp "$cert_file" "${cert_file}.bak.$(date +%Y%m%d)"
        cp "$key_file" "${key_file}.bak.$(date +%Y%m%d)"

        # 生成新密钥和证书(具体逻辑根据你的 PKI 方案)
        openssl genrsa -out "${key_file}.new" 2048
        # ... CSR 签发流程 ...

        # 原子替换
        mv "${key_file}.new" "$key_file"
        # mv "${cert_file}.new" "$cert_file"

        # 重载 dqlite 服务
        systemctl reload dqlite
        echo "Certificate renewed and service reloaded"
    else
        echo "Certificate valid for $days_left more days"
    fi
}

# 检查每个节点的证书
for node_dir in "$CERT_DIR"/node*/; do
    echo "Checking $node_dir..."
    check_and_renew \
        "${node_dir}/cert.pem" \
        "${node_dir}/key.pem" \
        "${CERT_DIR}/ca.crt"
done

8.5.3 安全审计日志

// 审计日志中间件
type AuditLogger struct {
    logger *log.Logger
}

func (a *AuditLogger) LogAccess(remoteAddr, operation, database, query string, success bool) {
    status := "SUCCESS"
    if !success {
        status = "FAILURE"
    }

    a.logger.Printf("[%s] %s | %s | %s | %s | %s",
        time.Now().Format(time.RFC3339),
        status,
        remoteAddr,
        operation,
        database,
        query,
    )
}

// 使用示例
auditLog := AuditLogger{logger: log.New(os.Stdout, "[AUDIT] ", 0)}

func handleQuery(remoteAddr, query string) {
    err := db.Exec(query)
    auditLog.LogAccess(remoteAddr, "EXEC", "mydb", query, err == nil)
}

8.6 安全事件响应

8.6.1 常见安全事件处理

事件 响应措施
证书泄露 立即吊销证书并重新签发
异常连接 阻断来源 IP,检查日志
数据泄露 评估影响范围,通知相关方
节点被入侵 隔离节点,从安全快照恢复
配置被篡改 从备份恢复配置

8.6.2 应急响应流程

发现异常
    │
    ├── 是否数据泄露? ──▶ 是 ──▶ 隔离系统 → 评估影响 → 通知
    │
    ├── 是否未授权访问? ──▶ 是 ──▶ 阻断来源 → 检查日志 → 加固
    │
    ├── 是否服务中断? ──▶ 是 ──▶ 检查集群 → 恢复服务
    │
    └── 否 → 记录日志 → 继续监控

本章小结

要点 说明
TLS 加密 节点间和客户端通信都应使用 TLS
双向认证 生产环境推荐 mTLS
网络隔离 防火墙限制 dqlite 端口访问
文件权限 使用专用用户、最小权限原则
证书管理 自动化轮换,定期审计
审计日志 记录所有访问和操作

下一章

第 9 章:Docker 与 Kubernetes 部署 — 学习如何使用 Docker Compose 和 Kubernetes 部署 dqlite 集群。