Skip to content
认证与安全

认证与安全

etcd 安全机制完整指南:TLS 传输加密、mTLS 双向认证、RBAC 用户/角色管理、认证启用与关闭,以及安全加固清单。


安全层次概览

┌──────────────────────────────────────────┐
│  ② RBAC 认证(用户 / 角色 / 权限)       │
├──────────────────────────────────────────┤
│  ① TLS 传输层(加密 + 身份验证)         │
│     - 单向 TLS(客户端验证服务端)       │
│     - 双向 TLS(mTLS,互相验证)         │
└──────────────────────────────────────────┘

TLS / mTLS

证书准备

# 使用 cfssl 生成自签名证书(或使用 openssl)
# 1. CA 证书(证书颁发机构)
cfssl gencert -initca ca-csr.json | cfssljson -bare ca

# 2. 服务端证书
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem server-csr.json | cfssljson -bare server

# 3. 客户端证书(mTLS 需要)
cfssl gencert -ca=ca.pem -ca-key=ca-key.pem client-csr.json | cfssljson -bare client

etcd 启动参数(TLS)

etcd \
  # 客户端 TLS(etcd ↔ 客户端之间)
  --cert-file=/etc/etcd/certs/server.pem \
  --key-file=/etc/etcd/certs/server-key.pem \
  --client-cert-auth=true \                 # 启用客户端证书认证
  --trusted-ca-file=/etc/etcd/certs/ca.pem \# 信任的 CA

  # 对端 TLS(etcd 节点之间,Always-on)
  --peer-cert-file=/etc/etcd/certs/server.pem \
  --peer-key-file=/etc/etcd/certs/server-key.pem \
  --peer-client-cert-auth=true \
  --peer-trusted-ca-file=/etc/etcd/certs/ca.pem \
  --peer-auto-tls                      # 自动生成自签名 peer 证书(仅测试)

Go 客户端 TLS 配置

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

    clientv3 "go.etcd.io/etcd/client/v3"
)

// 单向 TLS(客户端验证服务端)
tlsConfig := &tls.Config{
    RootCAs: loadCA("/etc/etcd/certs/ca.pem"),
}

// 双向 TLS(mTLS)
cert, _ := tls.LoadX509KeyPair("/etc/etcd/certs/client.pem", "/etc/etcd/certs/client-key.pem")
tlsConfig = &tls.Config{
    RootCAs:      loadCA("/etc/etcd/certs/ca.pem"),
    Certificates: []tls.Certificate{cert},
}

cli, err := clientv3.New(clientv3.Config{
    Endpoints: []string{"https://localhost:2379"},
    TLS:       tlsConfig,
})

func loadCA(path string) *x509.CertPool {
    caCert, _ := os.ReadFile(path)
    pool := x509.NewCertPool()
    pool.AppendCertsFromPEM(caCert)
    return pool
}

自动 TLS(仅测试用)

# 客户端自动生成自签名证书
etcd --auto-tls

# 对等节点自动生成自签名证书
etcd --peer-auto-tls

🚨 陷阱--auto-tls--peer-auto-tls 仅用于开发测试,生产环境必须使用正式的 CA 签发证书。


RBAC 认证

etcd 的权限模型:User(用户) × Role(角色)Permission(权限)

┌──────────┐   belongs to    ┌──────────┐   defines    ┌──────────────┐
│   User   │ ─────────────►  │   Role   │ ──────────►  │  Permission  │
│  (alice) │                 │  (admin) │              │  read /write │
└──────────┘                 └──────────┘              │  on key range│
                                                       └──────────────┘

认证工作流

1. AuthEnable → 启用认证
2. root 用户自动创建(首次启用时设置密码)
3. 客户端请求流程:
   ┌──────────┐    ┌──────────────┐    ┌──────────────┐
   │  Client  │───►│Authenticate()├───►│    Token     │
   │  (凭据)  │    │              │    │  (后续请求)  │
   └──────────┘    └──────────────┘    └──────┬───────┘
                                              │
                           ┌──────────────────▼──────────────────┐
                           │  每次请求携带 Token                  │
                           │  etcd 检查:                        │
                           │  ① 该用户是否有此 Key range 的权限?│
                           │  ② 该权限是否匹配当前操作?         │
                           └─────────────────────────────────────┘

root 用户

root 用户拥有全局读写权限,且不需要任何 Role。root 是 etcd 的超级管理员,只能有一个,不可删除。

root 能做且只有 root 能做

  • 启用/关闭认证
  • 管理用户和角色
  • 授予和撤销权限

Go 客户端 — 用户管理

import (
    clientv3 "go.etcd.io/etcd/client/v3"
    "go.etcd.io/etcd/api/v3/authpb"
)

// 添加用户
cli.UserAdd(ctx, "alice", "password123")

// 添加无密码用户
cli.UserAdd(ctx, "readonly", "")

// 修改密码
cli.UserChangePassword(ctx, "alice", "newpassword456")

// 获取用户信息
resp, _ := cli.UserGet(ctx, "alice")
fmt.Printf("用户: %s, 角色: %v\n", resp.Name, resp.Roles)

// 列出所有用户
resp2, _ := cli.UserList(ctx)
for _, name := range resp2.Users {
    fmt.Println(name)
}

// 授予/撤销角色
cli.UserGrantRole(ctx, "alice", "admin")
cli.UserRevokeRole(ctx, "alice", "admin")

// 删除用户
cli.UserDelete(ctx, "alice")

Go 客户端 — 角色管理

// 添加角色
cli.RoleAdd(ctx, "reader")

// 授予权限(只读 /config 前缀)
cli.RoleGrantPermission(ctx, "reader", "/config/",
    clientv3.WithPrefix(),     // 前缀匹配
    authpb.READ,               // 权限类型
)

// 授予读写权限
cli.RoleGrantPermission(ctx, "writer", "/data/",
    clientv3.WithPrefix(),
    authpb.READWRITE,
)

// 精确匹配单个 Key(不使用 WithPrefix)
cli.RoleGrantPermission(ctx, "single-key-role", "/global/status",
    nil,                       // 无 WithPrefix → 精确匹配
    authpb.READWRITE,
)

// 范围匹配 [start, end)
cli.RoleGrantPermission(ctx, "range-role", "/range/a",
    clientv3.WithRange("/range/z"),
    authpb.READ,
)

// 获取角色信息
resp, _ := cli.RoleGet(ctx, "reader")
for _, perm := range resp.Perm {
    fmt.Printf("Key=%s RangeEnd=%s PermType=%d\n",
        string(perm.Key), string(perm.RangeEnd), perm.PermType)
}

// 列出所有角色
resp2, _ := cli.RoleList(ctx)
for _, name := range resp2.Roles {
    fmt.Println(name)
}

// 撤销权限
cli.RoleRevokePermission(ctx, "reader", "/config/", clientv3.WithPrefix())

// 删除角色
cli.RoleDelete(ctx, "reader")

权限类型

类型 Go 常量 说明
READ authpb.READ 仅允许 Get / Watch
WRITE authpb.WRITE 仅允许 Put / Delete
READWRITE authpb.READWRITE 允许所有操作

启用/关闭认证

// 启用认证(需先创建 root 用户)
cli.AuthEnable(ctx)

// 关闭认证
cli.AuthDisable(ctx)

// 查看认证状态
resp, _ := cli.AuthStatus(ctx)
fmt.Printf("AuthEnabled=%v AuthRevision=%d\n", resp.Enabled, resp.AuthRevision)

RBAC 实战:最小权限模型

// 场景:配置中心 + 服务发现

// 1. 创建角色
// reader — 所有服务只读配置和服务信息
cli.RoleAdd(ctx, "reader")
cli.RoleGrantPermission(ctx, "reader", "/config/", clientv3.WithPrefix(), authpb.READ)
cli.RoleGrantPermission(ctx, "reader", "/service/", clientv3.WithPrefix(), authpb.READ)

// writer — 配置管理系统读写配置
cli.RoleAdd(ctx, "config-writer")
cli.RoleGrantPermission(ctx, "config-writer", "/config/", clientv3.WithPrefix(), authpb.READWRITE)
cli.RoleGrantPermission(ctx, "config-writer", "/service/", clientv3.WithPrefix(), authpb.READ)

// service — 服务实例注册和续约
cli.RoleAdd(ctx, "service")
cli.RoleGrantPermission(ctx, "service", "/service/", clientv3.WithPrefix(), authpb.READWRITE)
cli.RoleGrantPermission(ctx, "service", "/config/", clientv3.WithPrefix(), authpb.READ)

// 2. 创建用户并分配角色
cli.UserAdd(ctx, "svc-gateway", "pass123")
cli.UserGrantRole(ctx, "svc-gateway", "service")

cli.UserAdd(ctx, "config-manager", "pass456")
cli.UserGrantRole(ctx, "config-manager", "config-writer")

cli.UserAdd(ctx, "monitor", "pass789")
cli.UserGrantRole(ctx, "monitor", "reader")

// 3. 启用认证
cli.AuthEnable(ctx)

安全加固清单

项目 建议
🔒 不要将 etcd 暴露在公网 仅绑定内网 IP 或本地回环
🔒 生产环境开启 mTLS 客户端和服务端互相验证
🔒 开启 RBAC 认证 设置 root 密码,创建最小权限角色
🔒 根证书私钥离线保管 CA 私钥泄露意味着所有证书需要替换
🔒 客户端证书过期管理 设置短有效期证书 + 自动轮换机制
🔒 日志中脱敏 确保凭据不记录到日志
🔒 网络层面 防火墙限制 2379/2380 端口访问来源
🔒 定期审计用户和角色 移除不再需要的用户
🔒 审计日志 生产环境开启 audit log 记录访问操作

生产环境安全配置参考

etcd \
  --cert-file=/etc/etcd/certs/server.pem \
  --key-file=/etc/etcd/certs/server-key.pem \
  --client-cert-auth=true \
  --trusted-ca-file=/etc/etcd/certs/ca.pem \
  --peer-cert-file=/etc/etcd/certs/peer.pem \
  --peer-key-file=/etc/etcd/certs/peer-key.pem \
  --peer-client-cert-auth=true \
  --peer-trusted-ca-file=/etc/etcd/certs/ca.pem \
  --listen-client-urls=https://10.0.0.1:2379 \    # 仅内网
  --advertise-client-urls=https://10.0.0.1:2379 \  # 仅内网
  --listen-peer-urls=https://10.0.0.1:2380 \       # 仅内网
  --initial-advertise-peer-urls=https://10.0.0.1:2380

常见陷阱

陷阱 说明
🚨 忘记 root 密码 root 密码丢失后无法管理用户和角色,需要重建集群
🚨 Public discovery 泄漏信息 discovery.etcd.io 是公共服务,不要在 URL 中包含敏感信息
🚨 Auth 启用后客户端未重连 已连接的客户端在启用 Auth 后,如果之前未提供凭据,所有操作将失败
🚨 client-cert-auth 关闭时仍用证书连接 TLS 加密可以开启但客户端证书验证关闭,意味着"加密但不验证身份"
🚨 权限粒度理解错误 RoleGrantPermission(ctx, role, "/foo", nil, READ) 只允许读 /foo 这一个 Key,不包括 /foo/bar
🚨 auto-tls 上生产 自签名证书无身份验证作用,仅用于测试