整体架构

etcd 鉴权体系架构由控制面和数据面组成。(操作和资源?)

控制面:

image.png

你可以通过客户端工具 etcdctl 和鉴权 API 动态调整认证、鉴权规则,AuthServer 收到请求后,为了确保各节点间鉴权元数据一致性,会通过 Raft 模块进行数据同步

数据面鉴权流程:

  • 认证
    • 密码认证和证书认证
  • 授权流程

image.png

认证通过后,为了提高密码认证性能,会分配一个 Token(类似我们生活中的门票、通信证)给 client,client 后续其他请求携带此 Token,server 就可快速完成 client 的身份校验工作。

  • TokenProvider
    • SimpleToken
    • JWT

通过认证后,在访问 MVCC 模块之前,还需要通过授权流程。授权的目的是检查 client 是否有权限操作你请求的数据路径,etcd 实现了 RBAC 机制,支持为每个用户分配一个角色,为每个角色授予最小化的权限。

image.png

认证

密码认证

是最基础的鉴权的方式.

如何保障密码安全性?

  • hash
    • SHA-256
    • salt (对密码加盐后进行 hash)

etcd 的鉴权模块如何安全存储用户密码?

etcd 的用户密码存储正是融合了以上讨论的高安全性 hash 函数(Blowfish encryption algorithm)、随机的加盐 salt、可自定义的 hash 值计算迭代次数 cost。

例子:

  1. 首先你可以通过如下的 auth enable 命令开启鉴权,注意 etcd 会先要求你创建一个 root 账号,它拥有集群的最高读写权限。
  1. $ etcdctl user add root:root
  2. User root created
  3. $ etcdctl auth enable
  4. Authentication Enabled

启用鉴权后,这时 client 发起如下 put hello 操作时, etcd server 会返回”user name is empty”错误给 client,就初步达到了防止匿名用户访问你的 etcd 数据目的。

$ etcdctl put hello world
Error: etcdserver: user name is empty

etcd server 是在哪里做的鉴权的呢?

etcd server 收到 put hello 请求的时候,在提交到 Raft 模块前,它会从你请求的上下文中获取你的用户身份信息。如果你未通过认证,那么在状态机应用 put 命令的时候,检查身份权限的时候发现是空,就会返回此错误给 client。

给 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 里面,完成一个账号创建。

Authenticate 接口,它会验证你的身份合法性。

etcd 如何验证你密码正确性的呢?

鉴权模块首先会根据你请求的用户名 alice,从 boltdb 获取加密后的密码,因此 hash 值包含了算法版本、salt、cost 等信息,因此可以根据你请求中的明文密码,计算出最终的 hash 值,若计算结果与存储一致,那么身份校验通过。

如何提升密码认证性能

当 etcd server 验证用户密码成功后,它就会返回一个 Token 字符串给 client,用于表示用户的身份。后续请求携带此 Token,就无需再次进行密码校验,实现了通信证的效果。

Simple Token

Simple Token 的核心原理是当一个用户身份验证通过后,生成一个随机的字符串值 Token 返回给 client,并在内存中使用 map 存储用户和 Token 映射关系。当收到用户的请求时, etcd 会从请求中获取 Token 值,转换成对应的用户名信息,返回给下层模块使用。

如何应对 token 泄漏问题?

etcd 生成的每个 Token,都有一个过期时间 TTL 属性,Token 过期后 client 需再次验证身份,因此可显著缩小数据泄露的时间窗口,在性能上、安全性上实现平衡。

  • 在 etcd v3.4.9 版本中,Token 默认有效期是 5 分钟

JWT Token

JWT 是 Json Web Token 缩写, 它是一个基于 JSON 的开放标准(RFC 7519)定义的一种紧凑、独立的格式,可用于在身份提供者和服务提供者间,传递被认证的用户身份信息。它由 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

  1. 首先它将 header、payload 使用 base64 url 编码
  2. 然后将编码后的字符串用”.”连接在一起
  3. 最后用我们选择的签名算法比如 RSA 系列的私钥对其计算签名,输出结果即是 Signature
signature=RSA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
key) // 应该是私钥

JWT 就是由 base64UrlEncode(header).base64UrlEncode(payload).signature 组成。

证书认证

我们可以使用下面的 openssl 命令查看 client 证书的内容,下图是一个 x509 client 证书的内容,它含有证书版本、序列号、签名算法、签发者、有效期、主体名等信息,我们重点要关注的是主体名中的 CN 字段。

openssl x509 -noout -text -in client.pem

image.png

在 etcd 中,如果你使用了 HTTPS 协议并启用了 client 证书认证 (—client-cert-auth),它会取 CN 字段作为用户名,在我们的案例中,alice 就是 client 发送请求的用户名。

授权

当我们使用如上创建的 alice 账号执行 put hello 操作的时候,etcd 却会返回如下的”etcdserver: permission denied”无权限错误,这是为什么呢?

$ etcdctl put hello world --user alice:alice
Error: etcdserver: permission denied

这是因为开启鉴权后,put 请求命令在应用到状态机前,etcd 还会对发出此请求的用户进行权限检查, 判断其是否有权限操作请求的数据。常用的权限控制方法有 ACL(Access Control List)、ABAC(Attribute-based access control)、RBAC(Role-based access control),etcd 实现的是 RBAC 机制。

RBAC

什么是基于角色权限的控制系统 (RBAC) 呢?

它由下图中的三部分组成,User、Role、Permission。

  • User 表示用户,如 alice。
  • Role 表示角色,它是权限的赋予对象。
  • Permission 表示具体权限明细,比如赋予 Role 对 key 范围在[key,KeyEnd]数据拥有什么权限。

目前支持三种权限,分别是 READ、WRITE、READWRITE。

image.png

赋予权限例子:

$ #创建一个admin role 
etcdctl role add admin  --user root:root
Role admin created
# #分配一个可读写[hello,helly]范围数据的权限给admin role
$ etcdctl role grant-permission admin readwrite hello helly --user root:root
Role admin updated
# 将用户alice和admin role关联起来,赋予admin权限给user
$ etcdctl user grant-role alice admin --user root:root
Role admin is granted to user alice

因为有可能一个用户拥有成百上千个权限列表,etcd 为了提升权限检查的性能,引入了区间树,检查用户操作的 key 是否在已授权的区间,时间复杂度仅为 O(logN)。