一. 問題背景
當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。