云原生etcd基于用户角色控制权限

腾讯云云原生 etcd(Cloud Service for etcd)是基于 开源 etcd 针对云原生服务场景进行优化的 etcd 托管解决方案。具体介绍说明可以参考文档https://cloud.tencent.com/document/product/457/58176 。

云原生etcd默认提供2种访问,http和https的方式,https双向认证及鉴权,启用 HTTPS 的情况下,支持开启客户端证书认证。http没有鉴权,可以直接访问。

etcd还有一种访问方式,就是用户身份认证功能,但是云原生etcd没有提供这种配置方式,因为https默认就有证书认证了,不需要再额外配置认证了,所以我们这里基于http方式云原生etcd配置下身份认证,来给云原生etcd增加认证。

前提条件:

  • 腾讯云http方式访问的etcd实例
  • 配置了etcdctl的客户端机器

1. 创建root用户和角色

root用户拥有etcd的所有权限,且必须在激活身份认证之前就创建好. root用户的设计主要是出于管理的目的: 管理角色和普通用户. root用户必须具有root角色, 并且可以在etcd中进行任何修改。

代码语言:javascript
复制
# 添加root用户
# etcdctl --endpoints="http://172.16.10.41:2379" user add root
Password of root:
Type password of root again for confirmation:
User root created

添加root角色

etcdctl --endpoints="http://172.16.10.41" role add root

Role root created

root角色绑定到root用户

etcdctl --endpoints="http://172.16.10.41" user grant-role root root

Role root is granted to user root

开启auth

etcdctl --endpoints="http://172.16.10.41" auth enable

Authentication Enabled

没有开启认证前,直接设置key是正常的

开启auth后,这里访问etcd默认就需要加认证了,不然会报错

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" put c d
{"level":"warn","ts":"2023-06-23T14:31:21.660+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-11049eed-9e44-417b-bfa8-0fdf8ff23e3d/172.16.10.41:2379","attempt":0,"error":"rpc error: code = InvalidArgument desc = etcdserver: user name is empty"}
Error: etcdserver: user name is empty

访问etcd的数据,需要加上--user来认证

2. 创建用户

这里我们创建一个子用户,用来测试,创建用户时需要提供一个密码,如果使用 --interactive=false选项,支持从标准输入提供,也可以使用 --new-user-password 选项提供。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456  user add nwx --new-user-password 123456
User nwx created

如果用户不需要了,删除用户可以用如下命令删除。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456 user delete 用户名

3. 创建角色

接下来我们创建下角色,这里我们创建了3个角色,这里etcd角色可以被赋予3种权限,分别是read(读)、write(写)、readwrite(读和写)权限,这里3个角色分别对应不同的权限。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456  role add etcd-ro
Role etcd-ro created

etcdctl --endpoints="http://172.16.10.41" --user root:123456 role add etcd-wo

Role etcd-wo created

etcdctl --endpoints="http://172.16.10.41" --user root:123456 role add etcd-wr

Role etcd-wr created

如果角色不需要了,删除角色可以用如下命令删除。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456 role delete 角色名

4. 给角色授权

角色创建好之后,给角色授权下

  • etcd-ro授予读权限
  • etcd-wo授予写权限
  • etcd-wr授予读写权限

角色授权是基于具体的key的,首先我们创建2个key,分别是test1和test2来用于测试

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456 put /test1 hello1
OK

etcdctl --endpoints="http://172.16.10.41" --user root:123456 put /test2 hello2

OK

下面我们分别给角色分配test1这个key的权限

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456  role  grant-permission etcd-ro read /test1
Role etcd-ro updated

etcdctl --endpoints="http://172.16.10.41" --user root:123456 role grant-permission etcd-wo write /test1

Role etcd-wo updated

etcdctl --endpoints="http://172.16.10.41" --user root:123456 role grant-permission etcd-wr readwrite /test1

Role etcd-wr updated

上面是设置单个key,如果要设置多个key,可以参考grant-permission的用法说明

代码语言:javascript
复制
# etcdctl role grant-permission --help
NAME:
role grant-permission - Grants a key to a role
USAGE:
etcdctl role grant-permission [options] <role name> <permission type> <key> [endkey] [flags]
OPTIONS:
--from-key[=false] grant a permission of keys that are greater than or equal to the given key using byte compare
-h, --help[=false] help for grant-permission
--prefix[=false] grant a prefix permission

给以/foo/开头的所有key赋予读权限. The prefix is equal to the range [/foo/, /foo0)

etcdctl role grant-permission 角色名 --prefix=true read /foo/

给[key1, key5) 范围内的所有key赋予所有权限

etcdctl role grant-permission 角色名 readwrite key1 key5

赋予以/pub/可有的key所有权限

etcdctl role grant-permission 角色名 --prefix=true readwrite /pub/

如果需要给角色删除权限,可以用如下命令删除

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456 role revoke-permission 角色名 key

5. 给用户绑定角色测试权限

用户和角色都创建好了,我们给第一步创建的用户nwx绑定具体的角色来测试下权限是否生效。

给用户绑定和解除角色的命令如下

代码语言:javascript
复制
为用户添加角色
etcdctl user grant-role 用户名 角色名

为用户删除角色
etcdctl user revoke-role 用户名 角色名

5.1 用户绑定读权限的角色

这里首先给nwx绑定下etcd-ro角色,因为etcd-ro默认是分配的读权限

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456  user grant-role nwx etcd-ro
Role etcd-ro is granted to user nwx

etcdctl --endpoints="http://172.16.10.41" --user root:123456 user get nwx

User: nwx
Roles: etcd-ro

然后我们测试下访问test1和test2这2个key试试,正常只能读test1这个key,其他key权限都没有

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 get /test1
/test1
hello1

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 put /test1 hello1-1

{"level":"warn","ts":"2023-06-23T14:56:21.693+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-8d09a8b1-1a12-41f7-be25-aecc4909adba/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 get /test2

{"level":"warn","ts":"2023-06-23T14:56:33.235+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-47eb949b-240d-4e9c-9179-b540c7e99732/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 put /test2 hello2-1

{"level":"warn","ts":"2023-06-23T14:56:47.108+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-255ca16f-47bc-4ded-ba79-0f6ede414778/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

从测试结果看是符合角色权限设置预期的,只能读test1这个key。

5.2 用户绑定写权限的角色

这里继续测试下写权限的角色,我么给nwx绑定下etcd-wo角色,因为etcd-wo默认是分配的写权限,需要将etcd-ro的角色解绑掉,避免影响测试结果。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456  user grant-role nwx etcd-wo
Role etcd-wo is granted to user nwx

etcdctl --endpoints="http://172.16.10.41" --user root:123456 user revoke-role nwx etcd-ro

Role etcd-ro is revoked from user nwx

etcdctl --endpoints="http://172.16.10.41" --user root:123456 user get nwx

User: nwx
Roles: etcd-wo

然后我们测试下访问test1和test2这2个key试试,正常只能写test1这个key,其他key权限都没有。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 get /test1
{"level":"warn","ts":"2023-06-23T15:03:43.460+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-353c2a2e-7f00-4412-b373-17bb98968f41/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 put /test1 hello1-1

OK

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 get /test2

{"level":"warn","ts":"2023-06-23T15:04:02.877+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-2044c10e-d752-4a3e-b40f-cffb29fe405d/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 put /test2 hello2-1

{"level":"warn","ts":"2023-06-23T15:04:15.619+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-6a99f67d-b243-4d49-ac70-13b2dca8d0b5/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

从测试结果看是符合角色权限设置预期的,只能写test1这个key。

5.3 用户绑定读写权限的角色

这里测试下读写权限的角色,我么给nwx绑定下etcd-wr角色,因为etcd-wr默认是分配的读写权限,需要将etcd-wo的角色解绑掉,避免影响测试结果。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user root:123456  user revoke-role nwx etcd-wo
Role etcd-wo is revoked from user nwx

etcdctl --endpoints="http://172.16.10.41" --user root:123456 user grant-role nwx etcd-wr

Role etcd-wr is granted to user nwx

etcdctl --endpoints="http://172.16.10.41" --user root:123456 user get nwx

User: nwx
Roles: etcd-wr

然后我们测试下访问test1和test2这2个key试试,正常只能读写test1这个key,其他key的权限都没有。

代码语言:javascript
复制
# etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 get /test1
/test1
hello1-1

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 put /test1 hello1

OK

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 get /test2

{"level":"warn","ts":"2023-06-23T15:08:54.681+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-9da01b24-df4e-4d5c-bebb-37de9d9571e7/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

etcdctl --endpoints="http://172.16.10.41" --user nwx:123456 put /test2 hello2-1

{"level":"warn","ts":"2023-06-23T15:09:03.869+0800","caller":"clientv3/retry_interceptor.go:62","msg":"retrying of unary invoker failed","target":"endpoint://client-c2691c6e-39d6-4686-83f2-cf1b09d8e3b1/172.16.10.41:2379","attempt":0,"error":"rpc error: code = PermissionDenied desc = etcdserver: permission denied"}
Error: etcdserver: permission denied

从测试结果看是符合角色权限设置预期的,可以读写test1这个key,其他key的权限都没,并且这里查看test1的值是hello1-1,也验证了上一步的测试,给分配读权限是正常的。

上面就是如何通过用户和角色来控制权限,通过用户和角色可以更加灵活的来控制etcd里面的数据访问。