认证与安全
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 clientetcd 启动参数(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 上生产 | 自签名证书无身份验证作用,仅用于测试 |