一. 问题背景
当Zookeeper开启ACL后,Dubbo以及Elastic-Job需要如何配置来连接Zookeeper。
Zookeeper ACL介绍参考文章: https://cloud.tencent.com/developer/article/1414462
二. 组件版本
- zookeeper: 3.5.8
- dubbo: 2.6.6
- elastic-job: 1.0.6
三. ZK设置ACL
3.1 dubbo
dubbo默认是注册在/dubbo目录,下面以/dubbo目录为例说明。
1. 进入Zookeeper客户端,创建/dubbo目录及设置ACL权限。
$ create /dubbo 'data'
$ addauth digest hamawhite:123456
$ setAcl /dubbo auth:hamawhite:123456:cdwra
$ getAcl /dubbo
'digest,'hamawhite:YfjnOH3mtKecIPZ5prmNB7B6lA4=
: cdrwa
2. 修改SpringBoot的配置文件,增加duboo registry的username和password。
dubbo.registry.username=hamawhite
dubbo.registry.password=123456
如果Zookeeper管理员分配给指定的目录来注册,例如 /gou,则需要在dubbo的配置文件中还需增加 dubbo.registry.group=/shuqi 。
完整配置如下:
dubbo.registry.group=shuqi
dubbo.registry.username=hamawhite
dubbo.registry.password=123456
3.2 elastic-job
推荐把elastic-job的namespace配置为dubbo.registry.group的值,否则Zookeeper管理员还需要给elastic-job额外分配目录。
完整配置如下:
<!--配置作业注册中心 -->
<reg:zookeeper id="regCenter"
serverLists="${dubbo.registry.address}"
namespace="${dubbo.registry.group}"
digest="${dubbo.registry.username}:${dubbo.registry.password}"
baseSleepTimeMilliseconds="1000"
maxSleepTimeMilliseconds="3000"
maxRetries="0"/>
注意,在SpringBoot的配置中一定要定义dubbo.registry.group,如果是默认值,也要写dubbo.registry.group=dubbo。
四. ZK目录探索
4.1 dubbo registry目录
dubbo在/dubbo目录下注册的service都未设置ACL,权限都是world:anyone:cdrwa。这样其实任意用户能访问此service目录,仍有安全风险。
[zk: localhost:2181(CONNECTED) 64] getAcl /dubbo/com.dtwave.ai.api.service.ModelService
'world,'anyone
: cdrwa
[zk: localhost:2181(CONNECTED) 65] getAcl /dubbo/com.dtwave.ai.api.service.NotebookService
'world,'anyone
: cdrwa
4.2 dubbo 源码分析
dubbo2.6版本后Zookeeper客户端默认是Curator,因此查看com.alibaba.dubbo.remoting.zookeeper.curator.CuratorZookeeperClient的代码。
创建client代码如下:
public CuratorZookeeperClient(URL url) {
super(url);
try {
CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder()
.connectString(url.getBackupAddress())
.retryPolicy(new RetryNTimes(1, 1000))
.connectionTimeoutMs(5000);
String authority = url.getAuthority();
if (authority != null && authority.length() > 0) {
builder = builder.authorization("digest", authority.getBytes());
}
client = builder.build();
client.getConnectionStateListenable().addListener(new ConnectionStateListener() {
@Override
public void stateChanged(CuratorFramework client, ConnectionState state) {
if (state == ConnectionState.LOST) {
CuratorZookeeperClient.this.stateChanged(StateListener.DISCONNECTED);
} else if (state == ConnectionState.CONNECTED) {
CuratorZookeeperClient.this.stateChanged(StateListener.CONNECTED);
} else if (state == ConnectionState.RECONNECTED) {
CuratorZookeeperClient.this.stateChanged(StateListener.RECONNECTED);
}
}
});
client.start();
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
可以看到,创建client的时候有处理授权,builder = builder.authorization("digest", authority.getBytes()) ,也未设置aclProvider。
在创建目录时候,也未设置ACL。 因此子目录权限都是默认的world:anyone:cdrwa。
@Override
public void createPersistent(String path) {
try {
client.create().forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
@Override
public void createEphemeral(String path) {
try {
client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
} catch (NodeExistsException e) {
} catch (Exception e) {
throw new IllegalStateException(e.getMessage(), e);
}
}
补充创建目录可以通过配置withACL来设置ACL,示例代码如下:
具体可参考 https://blog.csdn.net/liuxiao723846/article/details/85303602
client.create().creatingParentsIfNeeded().withMode(CreateMode.PERSISTENT).withACL(acls).forPath(nodePath, nodeData);
4.3 elastic-job zookeeper目录
elastic-job在/dubbo目录下创建的子目录用户名和密码和配置项保持一致,permission权限都是cdrwa。
[zk: localhost:2181(CONNECTED) 66] getAcl /dubbo/updateNotebookStatusJob
'digest,'hamawhite:YfjnOH3mtKecIPZ5prmNB7B6lA4=
: cdrwa
[zk: localhost:2181(CONNECTED) 67] ls /dubbo/updateNotebookStatusJob
[config, execution, leader, servers]
[zk: localhost:2181(CONNECTED) 68] getAcl /dubbo/updateNotebookStatusJob/leader
'digest,'hamawhite:YfjnOH3mtKecIPZ5prmNB7B6lA4=
: cdrwa
4.4 elastic-job Zookeeper客户端源码分析
查看com.dangdang.ddframe.reg.zookeeper.ZookeeperRegistryCenter的代码。
创建client 核心代码如下:
public void init() {
......
if (!Strings.isNullOrEmpty(this.zkConfig.getDigest())) {
builder.authorization("digest", this.zkConfig.getDigest().getBytes(Charset.forName("UTF-8"))).aclProvider(new ACLProvider() {
public List<ACL> getDefaultAcl() {
return Ids.CREATOR_ALL_ACL;
}
public List<ACL> getAclForPath(String path) {
return Ids.CREATOR_ALL_ACL;
}
});
}
this.client = builder.build();
this.client.start();
try {
this.client.blockUntilConnected();
if (!Strings.isNullOrEmpty(this.zkConfig.getLocalPropertiesPath())) {
this.fillData();
}
} catch (Exception var3) {
RegExceptionHandler.handleException(var3);
}
}
可以看到,创建client的时候有处理授权,builder = builder.authorization("digest", authority.getBytes()) ,设置了aclProvider。
其中perms设置为全部权限,即cdrwa。Ids设置为AUTH_IDS,表示会继承客户端的身份鉴权信息。
/**
* This ACL gives the creators authentication id's all permissions.
*/
public final ArrayList<ACL> CREATOR_ALL_ACL = new ArrayList<ACL>(
Collections.singletonList(new ACL(Perms.ALL, AUTH_IDS)));
/**
* This Id is only usable to set ACLs. It will get substituted with the
* Id's the client authenticated with.
*/
public final Id AUTH_IDS = new Id("auth", "");
这样CreateBuilderImpl里的 acling = new ACLing(client.getAclProvider()),后续用client创建的子目录用户名和密码和配置项保持一致,permission权限都是cdrwa。