分布式服务Dubbo + Zookeeper安全认证

1,466 阅读8分钟
原文链接: mp.weixin.qq.com

前言

由于之前的服务都是在内网,Zookeeper集群配置都是走的内网IP,外网不开放相关端口。最近由于业务升级,购置了阿里云的服务,需要对外开放Zookeeper服务。

正文

问题

Zookeeper + Dubbo,如何设置安全认证?不想让其他服务连接Zookeeper,因为这个Zookeeper服务器在外网。

(一) 查询官方文档

Zookeeper 是 Apacahe Hadoop 的子项目,是一个树型的目录服务,支持变更推送,适合作为 Dubbo 服务的注册中心,工业强度较高,可用于生产环境,并推荐使用。

(二) 流程说明

  1. 服务提供者启动时: 向 /dubbo/com.foo.BarService/providers 目录下写入自己的 URL 地址

  2. 服务消费者启动时: 订阅 /dubbo/com.foo.BarService/providers 目录下的提供者 URL 地址。并向 /dubbo/com.foo.BarService/consumers 目录下写入自己的 URL 地址

  3. 监控中心启动时: 订阅 /dubbo/com.foo.BarService 目录下的所有提供者和消费者 URL 地址

(三)支持的功能

  • 当提供者出现断电等异常停机时,注册中心能自动删除提供者信息

  • 当注册中心重启时,能自动恢复注册数据,以及订阅请求

  • 当会话过期时,能自动恢复注册数据,以及订阅请求

  • 当设置 时,记录失败注册和订阅请求,后台定时重试

  • 可通过 设置 zookeeper 登录信息

  • 可通过 设置 zookeeper 的根节点,不设置将使用无根树

  • 支持 * 号通配符 ,可订阅服务的所有分组和所有版本的提供者

官网文档第五条,明确说明了可以通过 username 和 password 字段设置 zookeeper 登录信息。

以下是 registry 参数说明:

但是,如果在Zookeeper上通过digest方式设置ACL,然后在dubbo registry上配置相应的用户、密码,服务就注册不到Zookeeper上了,会报KeeperErrorCode = NoAuth错误。

但是查阅ZookeeperRegistry相关源码并没有发现相关认证的地方,搜遍全网很少有问类似的问题,这个问题似乎并没有多少人关注。

Zookeeper中的ACL

概述

传统的文件系统中,ACL分为两个维度,一个是属组,一个是权限,子目录/文件默认继承父目录的ACL。而在Zookeeper中,node的ACL是没有继承关系的,是独立控制的。Zookeeper的ACL,可以从三个维度来理解:一是 scheme;二是user;三是permission,通常表示为

  1. scheme:id:permissions

下面从这三个方面分别来介绍:

  • scheme: scheme对应于采用哪种方案来进行权限管理,zookeeper实现了一个 pluggable 的ACL方案,可以通过扩展 scheme ,来扩展ACL的机制。zookeeper-3.4.4 缺省支持下面几种 scheme:

  • world: 它下面只有一个 id, 叫 anyone,world:anyone 代表任何人,zookeeper 中对所有人有权限的结点就是属于world:anyone。

  • auth: 它不需要id,只要是通过authentication的user都有权限(zookeeper 支持通过 kerberos 来进行 authencation 也支持 username/password 形式的authentication)。

  • digest: 它对应的 id 为 username:BASE64(SHA1(password)),它需要先通过 username:password 形式的 authentication。

  • ip: 它对应的id为客户机的IP地址,设置的时候可以设置一个 ip 段,比如 ip:192.168.1.0/16,表示匹配前16个 bit 的 IP 段。

  • super: 在这种scheme情况下,对应的id拥有超级权限,可以做任何事情(cdrwa)。

  • permission: zookeeper目前支持下面一些权限:

CREATE(c): 创建权限,可以在在当前node下创建child node DELETE(d): 删除权限,可以删除当前的node READ(r): 读权限,可以获取当前node的数据,可以list当前node所有的child nodes WRITE(w): 写权限,可以向当前node写数据 ADMIN(a): 管理权限,可以设置当前node的permission

客户端管理

我们可以通过以下命令连接客户端进行操作:

  1. ./zkCli.sh

帮助

  1. [zk: localhost:2181(CONNECTED) 2] help

  2. ZooKeeper -server host:port cmd args

  3.        connect host:port

  4.        get path [watch]

  5.        ls path [watch]

  6.        set path data [version]

  7.        rmr path

  8.        delquota [-n|-b] path

  9.        quit

  10.        printwatches on|off

  11.        create [-s] [-e] path data acl

  12.        stat path [watch]

  13.        close

  14.        ls2 path [watch]

  15.        history

  16.        listquota path

  17.        setAcl path acl

  18.        getAcl path

  19.        sync path

  20.        redo cmdno

  21.        addauth scheme auth

  22.        delete path [version]

  23.        setquota -n|-b val path

简单操作

  1. [zk: localhost:2181(CONNECTED) 12] ls /

  2. [dubbo, test, zookeeper]

  3. [zk: localhost:2181(CONNECTED) 13] create /itstyle  data ip:192.168.1.190:cdrw

  4. Created /itstyle

  5. [zk: localhost:2181(CONNECTED) 14] getAcl /itstyle

  6. 'ip,'192.168.1.190

  7. : cdrw

zkclient操作代码

  1. import java.security.NoSuchAlgorithmException;

  2. import java.util.ArrayList;

  3. import java.util.List;

  4. import java.util.Map;

  5. import org.I0Itec.zkclient.ZkClient;

  6. import org.apache.zookeeper.ZooDefs;

  7. import org.apache.zookeeper.data.ACL;

  8. import org.apache.zookeeper.data.Id;

  9. import org.apache.zookeeper.data.Stat;

  10. import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;

  11. public class Acl {

  12.    private static final String zkAddress = "192.168.1.190:2181";  

  13.    private static final String testNode = "/dubbo";  

  14.    private static final String readAuth = "read-user:123456";  

  15.    private static final String writeAuth = "write-user:123456";  

  16.    private static final String deleteAuth = "delete-user:123456";  

  17.    private static final String allAuth = "super-user:123456";  

  18.    private static final String adminAuth = "admin-user:123456";  

  19.    private static final String digest = "digest";  

  20.    private static void initNode() throws NoSuchAlgorithmException {  

  21.        ZkClient zkClient = new ZkClient(zkAddress);  

  22.        System.out.println(DigestAuthenticationProvider.generateDigest(allAuth));

  23.        zkClient.addAuthInfo(digest, allAuth.getBytes());  

  24.        if (zkClient.exists(testNode)) {  

  25.            zkClient.delete(testNode);  

  26.            System.out.println("节点删除成功!");  

  27.        }  

  28.        List<ACL> acls = new ArrayList<ACL>();  

  29.        acls.add(new ACL(ZooDefs.Perms.ALL, new Id(digest, DigestAuthenticationProvider.generateDigest(allAuth))));

  30.        acls.add(new ACL(ZooDefs.Perms.ALL, new Id(digest, DigestAuthenticationProvider.generateDigest(allAuth))));  

  31.        acls.add(new ACL(ZooDefs.Perms.READ, new Id(digest, DigestAuthenticationProvider.generateDigest(readAuth))));  

  32.        acls.add(new ACL(ZooDefs.Perms.WRITE, new Id(digest, DigestAuthenticationProvider.generateDigest(writeAuth))));  

  33.        acls.add(new ACL(ZooDefs.Perms.DELETE, new Id(digest, DigestAuthenticationProvider.generateDigest(deleteAuth))));  

  34.        acls.add(new ACL(ZooDefs.Perms.ADMIN, new Id(digest, DigestAuthenticationProvider.generateDigest(adminAuth))));  

  35.        zkClient.createPersistent(testNode, testNode, acls);  

  36.        System.out.println(zkClient.readData(testNode));  

  37.        System.out.println("节点创建成功!");  

  38.        zkClient.close();  

  39.    }  

  40.    private static void readTest() {  

  41.        ZkClient zkClient = new ZkClient(zkAddress);  

  42.        try {  

  43.            System.out.println(zkClient.readData(testNode));//没有认证信息,读取会出错  

  44.        } catch (Exception e) {  

  45.            System.err.println(e.getMessage());  

  46.        }  

  47.        try {  

  48.            zkClient.addAuthInfo(digest, adminAuth.getBytes());  

  49.            System.out.println(zkClient.readData(testNode));//admin权限与read权限不匹配,读取也会出错  

  50.        } catch (Exception e) {  

  51.            System.err.println(e.getMessage());  

  52.        }  

  53.        try {  

  54.            zkClient.addAuthInfo(digest, readAuth.getBytes());  

  55.            System.out.println(zkClient.readData(testNode));//只有read权限的认证信息,才能正常读取  

  56.        } catch (Exception e) {  

  57.            System.err.println(e.getMessage());  

  58.        }  

  59.        zkClient.close();  

  60.    }  

  61.    private static void writeTest() {  

  62.        ZkClient zkClient = new ZkClient(zkAddress);  

  63.        try {  

  64.            zkClient.writeData(testNode, "new-data");//没有认证信息,写入会失败  

  65.        } catch (Exception e) {  

  66.            System.err.println(e.getMessage());  

  67.        }  

  68.        try {  

  69.            zkClient.addAuthInfo(digest, writeAuth.getBytes());  

  70.            zkClient.writeData(testNode, "new-data");//加入认证信息后,写入正常  

  71.        } catch (Exception e) {  

  72.            System.err.println(e.getMessage());  

  73.        }  

  74.        try {  

  75.            zkClient.addAuthInfo(digest, readAuth.getBytes());  

  76.            System.out.println(zkClient.readData(testNode));//读取新值验证  

  77.        } catch (Exception e) {  

  78.            System.err.println(e.getMessage());  

  79.        }  

  80.        zkClient.close();  

  81.    }  

  82.    private static void deleteTest() {  

  83.        ZkClient zkClient = new ZkClient(zkAddress);  

  84.        zkClient.addAuthInfo(digest, deleteAuth.getBytes());  

  85.        try {  

  86.            System.out.println(zkClient.readData(testNode));  

  87.            zkClient.delete(testNode);  

  88.            System.out.println("节点删除成功!");  

  89.        } catch (Exception e) {  

  90.            System.err.println(e.getMessage());  

  91.        }  

  92.        zkClient.close();  

  93.    }  

  94.    private static void changeACLTest() {  

  95.        ZkClient zkClient = new ZkClient(zkAddress);  

  96.        //注:zkClient.setAcl方法查看源码可以发现,调用了readData、setAcl二个方法  

  97.        //所以要修改节点的ACL属性,必须同时具备read、admin二种权限  

  98.        zkClient.addAuthInfo(digest, adminAuth.getBytes());  

  99.        zkClient.addAuthInfo(digest, readAuth.getBytes());  

  100.        try {  

  101.            List<ACL> acls = new ArrayList<ACL>();  

  102.            acls.add(new ACL(ZooDefs.Perms.ALL, new Id(digest, DigestAuthenticationProvider.generateDigest(adminAuth))));  

  103.            zkClient.setAcl(testNode, acls);  

  104.            Map.Entry<List<ACL>, Stat> aclResult = zkClient.getAcl(testNode);  

  105.            System.out.println(aclResult.getKey());  

  106.        } catch (Exception e) {  

  107.            System.err.println(e.getMessage());  

  108.        }  

  109.        zkClient.close();  

  110.    }  

  111.    public static void main(String[] args) throws Exception {

  112.        initNode();

  113.        System.out.println("---------------------");

  114.        readTest();

  115.        System.out.println("---------------------");

  116.        writeTest();

  117.        System.out.println("---------------------");

  118.        changeACLTest();

  119.        System.out.println("---------------------");

  120.        deleteTest();  

  121.    }  

  122. }

总结

大部分服务大都是部署在内网的,基本很少对外网开放,然而 Dubbo 的 zookeeper 用户权限认证貌似真的不起作用,如果非要对外开放只能通过 iptables 或者 firewall 进行 IP Access Control,如果是阿里云服务器的话安全组也是个不错的选择。


欢迎关注技术公众号: 零壹技术栈

本帐号将持续分享后端技术干货,包括虚拟机基础,多线程编程,高性能框架,异步、缓存和消息中间件,分布式和微服务,架构学习和进阶等学习资料和文章。