etcd 鉴权体系架构由控制面和数据面组成。

上图是是 etcd 鉴权体系控制面,你可以通过客户端工具 etcdctl 和鉴权 API 动态调整认证、鉴权规则,AuthServer 收到请求后,为了确保各节点间鉴权元数据一致性,会通过 Raft 模块进行数据同步。
当对应的 Raft 日志条目被集群半数以上节点确认后,Apply 模块通过鉴权存储 (AuthStore) 模块,执行日志条目的内容,将规则存储到 boltdb 的一系列“鉴权表”里面。
下图是数据面鉴权流程,由认证和授权流程组成。认证的目的是检查 client 的身份是否合法、防止匿名用户访问等。
目前 etcd 实现了两种认证机制,分别是密码认证和证书认证。

认证通过后,为了提高密码认证性能,会分配一个 Token(类似我们生活中的门票、通信证)给 client,client 后续其他请求携带此 Token,server 就可快速完成 client 的身份校验工作。
实现分配 Token 的服务也有多种,这是 TokenProvider 所负责的,目前支持 SimpleToken 和 JWT 两种。
通过认证后,在访问 MVCC 模块之前,还需要通过授权流程。
授权的目的是检查 client 是否有权限操作你请求的数据路径,etcd 实现了 RBAC 机制,支持为每个用户分配一个角色,为每个角色授予最小化的权限。

etcd 支持为每个用户分配一个账号名称、密码。但密码认证存在两大难点,它们分别是如何保障密码安全性和提升密码认证性能。
etcd 的用户密码存储融合了高安全性 hash 函数(Blowfish encryption algorithm)、随机的加盐 salt、可自定义的 hash 值计算迭代次数 cost。
创建 etcd 用户并启用 etcd 鉴权
# etcd 创建其他帐号前,需要先创建一个 root 帐号,有点类似 linux 了
$ etcdctl user add root:root
User root created
$ etcdctl auth enable
Authentication Enabled
启用鉴权后,etcd server 收到 put hello 请求的时候,在提交到 Raft 模块前,它会从你请求的上下文中获取你的用户身份信息。
如果你未通过认证,那么在状态机应用 put 命令的时候,检查身份权限的时候发现是空,就会返回"user name is empty"错误给 client。
下面我通过鉴权模块的 user 命令,给 etcd 增加一个 alice 账号。我们一起来看看 etcd 鉴权模块是如何基于我上面介绍的技术方案,来安全存储 alice 账号信息。
$ etcdctl user add alice:alice --user root:root
User alice created
鉴权模块收到此命令后,它会使用 bcrpt 库的 blowfish 算法,基于明文密码、随机分配的 salt、自定义的 cost、迭代多次计算得到一个 hash 值,并将加密算法版本、salt 值、cost、hash 值组成一个字符串,作为加密后的密码。
最后,鉴权模块将用户名 alice 作为 key,用户名、加密后的密码作为 value,存储到 boltdb 的 authUsers bucket 里面,完成一个账号创建。
当你使用 alice 账号访问 etcd 的时候,你需要先调用鉴权模块的 Authenticate 接口,它会验证你的身份合法性。
登陆时,鉴权模块首先会根据你请求的用户名 alice,从 boltdb 获取加密后的密码,因此 hash 值包含了算法版本、salt、cost 等信息,因此可以根据你请求中的明文密码,计算出最终的 hash 值,若计算结果与存储一致,那么身份校验通过。
为了减少频繁且昂贵的密码计算匹配、提高密码认证性能,etcd在用户密码验证成功后返回一个Token字符串给客户端,用于后续请求的身份验证,避免了每次请求都进行密码校验。
etcd目前支持两种类型的Token:Simple Token和JWT Token
Simple Token 的工作原理是在用户身份验证通过后,生成一个随机字符串 Token 返回给客户端,并在 etcd 服务器内存中以 map 形式存储用户与 Token 的映射关系。当接收到请求时,etcd 通过 Token 获取对应的用户名信息进行处理。为应对潜在的安全风险,每个 Token 都有 TTL 属性,过期后需重新验证用户身份,以减少数据泄露的风险窗口。在 etcd v3.4.9 版本中,默认 Token 有效期为 5 分钟。
然而,Simple Token 存在一些不足之处:
它是有状态的,需要服务器内存来维护 Token 和用户名之间的映射关系。
可描述性弱,客户端无法从 Token 直接获取如过期时间、用户名或签发者等信息,这导致客户端难以预判 Token 失效并提前采取措施避免请求失败。
鉴于这些限制,Simple Token 更适合于开发和测试环境,而不推荐用于生产环境,因为它在性能和安全性上可能达不到生产级别的要求。
JWT(Json Web Token)是一种基于 JSON 的开放标准(RFC 7519),用于在身份提供者和服务提供者间传递被认证的用户身份信息。JWT 格式紧凑且独立,由 Header、Payload 和 Signature 三个部分组成,每个都是一个 JSON 结构体。
Header 包含 alg(签名算法,etcd 支持 RSA、ESA、PS 系列等)和 typ(类型为 JWT)字段。
示例:
{
"alg": "RS256",
"typ": "JWT"
}
Payload 是载荷部分,包含如用户名、过期时间等信息,并支持自定义字段。
示例:
{
"username": username,
"revision": revision,
"exp": time.Now().Add(t.ttl).Unix(),
}
Signature 签名是通过将 Header 和 Payload 使用 Base64 URL 编码后,用“.”连接起来,并采用指定的签名算法(例如 RSA 系列的私钥)计算得出。
其输出结果是:
signature=RSA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
key)
JWT 的格式为 base64UrlEncode(header).base64UrlEncode(payload).signature。由于其自带用户名、过期时间等描述信息,使得 JWT 成为一种无状态的令牌,服务端无需保存它,客户端可以方便高效地获取 Token 的相关信息。
JWT 解决了 Simple Token 的一些不足之处,提供了更高的安全性,因此 etcd 社区建议在生产环境中使用密码认证时选择 JWT Token(通过 --auth-token 'jwt' 设置),而不是默认的 Simple Token。
密码认证通常使用在 client 和 server 基于 HTTP 协议通信的内网场景中。当对安全有更高要求时,需要使用 HTTPS 协议加密通信数据,以防止中间人攻击和数据被篡改等安全风险。
HTTPS 利用非对称加密实现身份认证和密钥协商,因此在使用 HTTPS 协议时,需通过 CA 证书给 client 生成证书才能访问。
一个 client 证书包含的信息有证书版本、序列号、签名算法、签发者、有效期、主体名等。特别地,在使用证书认证时,etcd server 如何确定你发送请求对应的用户名呢?我们可以通过下面的 openssl 命令查看 client 证书的内容:
$ openssl x509 -noout -text -in client.pem
Certificate:
Data:
Version: 3 (0x2)
Serial Number:
12:34:56:78:90:ab:cd:ef
Signature Algorithm: sha256WithRSAEncryption
Issuer: C=US, ST=California, O=MyOrganization, OU=MyOrganizationalUnit, CN=MyCA
Validity
Not Before: Feb 6 00:00:00 2025 GMT
Not After : Feb 6 23:59:59 2026 GMT
Subject: C=US, ST=California, O=MyOrganization, OU=MyOrganizationalUnit, CN=alice
Subject Public Key Info:
Public Key Algorithm: rsaEncryption
RSA Public Key: (2048 bit)
...
X509v3 extensions:
X509v3 Basic Constraints:
CA:FALSE
X509v3 Key Usage:
Digital Signature, Non Repudiation, Key Encipherment
X509v3 Extended Key Usage:
TLS Web Client Authentication
X509v3 Subject Alternative Name:
othername:
Signature Algorithm: sha256WithRSAEncryption
...
对于一个 X.509 client 证书,其主体名中的 CN 字段尤为重要。在 etcd 中,如果你使用了 HTTPS 协议并启用了 client 证书认证(--client-cert-auth),etcd server 会将 CN 字段作为用户名。
例如,在我们的案例中,alice 就是 client 发送请求的用户名。这使得通过 HTTPS 加密通信的同时,也能确保用户身份的正确识别。
证书认证在稳定性、性能上都优于密码认证。
稳定性上,它不存在 Token 过期、使用更加方便、会让你少踩坑,避免了不少 Token 失效而触发的 Bug。
性能上,证书认证无需像密码认证一样调用昂贵的密码认证操作 (Authenticate 请求),此接口支持的性能极低。
当我们使用创建的 alice 账号执行 put hello 操作的时候,etcd 却会返回如下的 "etcdserver: permission denied" 无权限错误。
$ etcdctl put hello world --user alice:alice
Error: etcdserver: permission denied
这是因为开启鉴权后,etcd 在将请求应用到状态机前会对用户进行权限检查,以验证该用户是否有操作请求数据的权限。
etcd 实现了基于角色的访问控制(RBAC)机制来进行权限管理。因此,若想让 alice 能够执行 put 操作,需要先为其分配相应的角色和权限。这不同于 ACL(访问控制列表)或 ABAC(基于属性的访问控制)方法,etcd 通过 RBAC 提供了一种灵活且易于管理的权限控制方式。
它由下图中的三部分组成,
User、Role、Permission。User 表示用户,如 alice。
Role 表示角色,它是权限的赋予对象。
Permission 表示具体权限明细,比如赋予 Role 对 key 范围在 [key,KeyEnd] 数据拥有什么权限。目前支持三种权限,
READ
WRITE
READWRITE

下面是一个使用 etcd 的 RBAC 机制来为 alice 用户赋予对 [hello, helly] 数据范围的读写权限为例子:
创建一个名为 admin 的角色:
etcdctl role add admin --user root:root
给 admin 角色分配对 [hello, helly] 范围数据的读写权限:
etcdctl role grant-permission admin readwrite hello helly --user root:root
将 admin 角色授予 alice 用户:
etcdctl user grant-role alice admin --user root:root
完成以上步骤后,当使用 alice 用户执行 put hello 操作时,etcd 鉴权模块会查询并检查 alice 的权限列表。为提高权限检查效率,etcd 使用区间树来确保时间复杂度仅为 O(logN)。在本例中,由于 hello 在 admin 角色被授权的 [hello, helly] 区间内,因此该操作会被允许。
然而,尝试更新不在授权区间内的 key(如 hey),将导致权限检查失败,并返回 "etcdserver: permission denied" 错误:
$ etcdctl put hey hey --user alice:alice
Error: etcdserver: permission denied
上面展示了如何通过 RBAC 机制有效管理用户的权限,确保他们只能访问或修改被明确授权的数据范围。
etcd 鉴权模块的设计和实现主要关注安全性、性能、扩展性和一致性四大方面。
安全性:旨在防止恶意行为如绕过鉴权、伪造、篡改或越权等。etcd 通过安全加密存储密码、证书认证及 RBAC 实现了高度的安全性,确保即使在数据库泄露的情况下影响也能控制在最小范围内。
性能:为了不成为业务性能的瓶颈,etcd 使用 Token 减少了频繁的密码验证开销,并支持证书认证以适应不同规模的业务需求。特别是利用 CN 字段作为用户名的方法,解决了较大规模业务场景下的 Token 过期问题。
扩展性:考虑到复杂的业务场景,etcd 的权限控制系统设计得非常灵活且具有良好的扩展性。RBAC 和 Token Provider 机制允许根据实际需要进行调整,使得用户权限管理更加精细,实现了权限的最小化分配。
一致性:为确保鉴权数据在所有节点上的一致性,etcd 在 v3 版本中通过 Raft 模块同步鉴权指令日志,解决了早期版本因鉴权命令未经过 Raft 导致的数据不一致问题。
本文内容摘抄自极客时间专栏 etcd 实战课