ZooKeeper安全认证机制:ZNode ACL
ZooKeeper的Client-Server互认证机制是从3.4.0版本开始引入的,本文主要介绍znodes的ACL的定义,任务服务接口定义与几种已有的认证服务实现,以及ACL与多种认证服务是如何建立联系的。本文内容基于ZooKeeper 3.5.1版本。
ACL
ZooKeeper的ACL可针对znodes
设置相应的权限信息。ACL数据的表示格式为:schema:id:permissions
-
schema 支持的几种schema为:
- world
只有一个名为
anyone
的Id
,world:anyone
代表任何人,也就是说,对应节点任何人可访问- auth
代表任何通过认证的用户,该schema不需要配置
Id
信息- digest
基于
username:password
生成的MD5 Hash值作为Id
信息,认证基于username:password
明文认证,但在acl中存储的是username:base64(password)
- ip
基于IP地址作为
Id
,支持IP地址或IP地址段 - id 代表用户
- permissions 权限定义为(READ, WRITE, CREATE, DELETE, ADMIN, ALL)
由ACL的定义信息,可以看出来,ZooKeeper可以针对不同的znodes
来提供不同的认证机制。
AuthenticationProvider
每一种认证服务均需要实现AuthenticationProvider
接口来支持一种新的schema,所有的AuthenticationProvider
实现类都被注册在ProviderRegistry
中。ZooKeeper中已经提供的AuthenticationProvider`的实现类:
每一个AuthenticationProvider
实现类所关联的schema
如下所示:
DigestAuthenticationProvider | digest |
IPAuthenticationProvider | ip |
SASLAuthenticationProvider | sasl |
X509AuthenticationProvider | x509 |
当znode acl schema为world
时,是不需要经任何AuthenticationProvider
进行认证的,因此不需要任何实现类。
当znode acl schema为auth
时,代表着需要对请求上下文中的认证信息进行校验,在ServerCnxn
的authInfo
中保存了所有的已认证成功的Id
以及认证服务所关联的的schema
,由该schema
再去ProviderRegistry
中查找所关联的AuthenticationProvider
实现类来对认证信息进行校验。
除了上述已有的实现者以外,用户还可以自定义实现AuthenticationProvider
。自定义的实现类,需要设置到System Properties中,对应的Property Key
需以"zookeeper.authProvider."
开头。另外,自定义的AuthenticationProvider
的schema
名称不应与现有的重名,否则会覆盖现有的实现。
Reference
ZooKeeper安全认证机制:用户名密码认证
ZooKeeper提供了简单的基于用户名和密码的认证机制,即DIGEST-MD5认证机制。本文首先介绍使用该认证机制所涉及的一些配置细节,接下来介绍ZooKeeper内部关于DIGEST-MD5认证机制的一些实现细节。
如何使用
Client
系统属性配置:
// "zookeeper.sasl.clientconfig"如果不设置,默认值为"Client" System.setProperty("zookeeper.sasl.clientconfig", "Client"); System.setProperty("zookeeper.sasl.client", "true");
自定义一个JaasConf对象,继承自javax.security.auth.login.Configuration,目的是为了便于Configuration所需参数的配置:
public class JaasConf extends Configuration { private Map<String, AppConfigurationEntry[]> sections = new HashMap<String, AppConfigurationEntry[]>(); public void addSection(String name, String loginModuleName, String... args) { Map<String, String> options = new HashMap<String, String>(); for (int i = 0; i < args.length; i += 2) { options.put(args[i], args[i + 1]); } AppConfigurationEntry[] entries = new AppConfigurationEntry[]{ new AppConfigurationEntry(loginModuleName, AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, options)}; this.sections.put(name, entries); } @Override public AppConfigurationEntry[] getAppConfigurationEntry(String name) { return this.sections.get(name); } }
实例化JaasConf,设置LoginModuleName以及对应的username/password等信息:
JaasConf conf = new JaasConf(); // Section Name: "Client", 这里的名称与系统属性"zookeeper.sasl.clientconfig"保持一致 // LoginModule Name: "org.apache.zookeeper.server.auth.DigestLoginModule" // Options: // "username": "nosql" // "password": "nosql123" conf.addSection("Client", "org.apache.zookeeper.server.auth.DigestLoginModule", "username", "nosql", "password", "nosql123"); Configuration.setConfiguration(conf);
Server
系统属性配置:
System.setProperty("zookeeper.sasl.serverconfig", "Server"); System.setProperty("zookeeper.authProvider.sasl", "org.apache.zookeeper.server.auth.SASLAuthenticationProvider");
实例化JaasConf,并在Server端配置所有允许访问的username/password信息:
JaasConf conf = new JaasConf(); // LoginModuleName: "org.apache.zookeeper.server.auth.DigestLoginModule" // Options: // "user_nosql: nosql123" conf.addSection("Server", "org.apache.zookeeper.server.auth.DigestLoginModule", "user_nosql", "nosql123"); Configuration.setConfiguration(conf);
可以看到,Client端与Server端配置username/password的参数名称是不同的:
- Client 用户名通过静态参数”username“指定,密码通过静态参数”password“指定
- Server 用户名直接配置在一个以”user_“开头的动态参数名中,参数值直接为对应的password
Client通过这种模式只能配置一个username/password,而Server端的动态参数则允许配置多个Client的username/password。原因在于,Client只需要配置一个username/password即可,而Server端则允许配置多个Client的username/password。
实现原理
整体思路
- Server端在初始化ServerCnxnFactory时,加载预先配置的允许访问的一个或多个username/password列表,并执行Login操作
- Client基于配置的username/password以及DigestLoginModule,执行Login操作
- Client请求与Server端建立Sasl连接,建立连接过程中,通过com.sun.security.sasl.digest.FactoryImpl提供的认证机制,完成对username/password的合法校验
Client初始化
ZooKeeperSaslClient初始化时:
if (login == null) { if (LOG.isDebugEnabled()) { LOG.debug("JAAS loginContext is: " + loginContext); } // 初始化Login对象,Login对象是static类型的,也就说,该对象在进程级别内 // 是共享的. Login对象利用Java JAAS机制执行login操作,具体的Login机制由 // 配置的LoginContext来实现. login = new Login(loginContext, new ClientCallbackHandler(null)); login.startThreadIfNeeded(); } Subject subject = login.getSubject(); SaslClient saslClient; // ZooKeeper支持的认证主要是GSSAPI(Kerberos)以及DIGEST-MD5. 如果基于GSSAPI, // 认证成功后会在Subject中添加对应的Principal信息. 如果Subject中的Principal // 信息为空,则认为要使用DIGEST-MD5认证(注: 这种设计并不太好) if (subject.getPrincipals().isEmpty()) { // no principals: must not be GSSAPI: use DIGEST-MD5 mechanism instead. LOG.info("Client will use DIGEST-MD5 as SASL mechanism."); String[] mechs = {"DIGEST-MD5"}; // 从subject中获取username与password信息 String username = (String)(subject.getPublicCredentials().toArray()[0]); String password = (String)(subject.getPrivateCredentials().toArray()[0]); // 初始化SaslClient时,将username传入,password在ClientCallbackHandler中. // "zk-sasl-md5" is a hard-wired 'domain' parameter shared with // zookeeper server code (see ServerCnxnFactory.java) saslClient = Sasl.createSaslClient(mechs, username, "zookeeper", "zk-sasl-md5", null, new ClientCallbackHandler(password)); return saslClient; }
关于如上源码的更多备注信息:
- Login阶段,已经配置了LoginModule为
org.apache.zookeeper.server.auth.DigestLoginModule
-
DigestLoginModule
中在初始化时已经将Client配置的username和password信息加载到subject中:public void initialize(Subject subject, CallbackHandler callbackHandler, Map<String,?> sharedState, Map<String,?> options) { if (options.containsKey("username")) { // Zookeeper client: get username and password from JAAS conf // (only used if using DIGEST-MD5). this.subject = subject; String username = (String)options.get("username"); this.subject.getPublicCredentials().add((Object)username); String password = (String)options.get("password"); this.subject.getPrivateCredentials().add((Object)password); } return; }
- Sasl.createSaslClient的流程:
String mechFilter = "SaslClientFactory." + mechName; Provider[] provs = Security.getProviders(mechFilter); for (int j = 0; provs != null && j < provs.length; j++) { className = provs[j].getProperty(mechFilter); if (className == null) { // Case is ignored continue; } fac = (SaslClientFactory) loadFactory(provs[j], className); if (fac != null) { mech = fac.createSaslClient( new String[]{mechanisms[i]}, authorizationId, protocol, serverName, props, cbh); if (mech != null) { return mech; } } }
“SaslClientFactory.DEGIEST-MD5″所关联的SaslClientFactory实现为:
com.sun.security.sasl.digest.FactoryImpl
所有的SaslClientFactory的实现信息都被注册在java.security.Security中。
Security与ProviderRegistry:
java.security.Security: Java Security框架中的定义,用来注册SaslClientFactory. 每一个SaslClientFactory都关联着一个Name.
org.apache.zookeeper.server.auth.ProviderRegistry: ZooKeeper中自定义的用来注册所有的AuthenticationProvider的类,每一个AuthenticationProvider关联一个schema
Server端初始化
ServerCnxnFactory#configureSaslLogin中的一些关键源码:
String serverSection = System.getProperty("zookeeper.sasl.serverconfig", "Server"); // Note that 'Configuration' here refers to javax.security.auth.login.Configuration. AppConfigurationEntry entries[] = null; SecurityException securityException = null; try { entries = Configuration.getConfiguration().getAppConfigurationEntry(serverSection); } catch (SecurityException e) { // handle below: might be harmless if the user doesn't intend to use JAAS authentication. securityException = e; } // ...中间略去一下非关键源码.... try { // 初始化SaslServerCallbackHandler saslServerCallbackHandler = new SaslServerCallbackHandler(Configuration.getConfiguration()); // 初始化Login对象,利用配置的LoginModule执行login操作. login = new Login(serverSection, saslServerCallbackHandler); login.startThreadIfNeeded(); } catch (LoginException e) { // .... }
SaslServerCallbackHandler
初始化过程中,加载配置的一个或多个username/password信息:
public SaslServerCallbackHandler(Configuration configuration) throws IOException { String serverSection = System.getProperty("zookeeper.sasl.serverconfig", "Server"); AppConfigurationEntry configurationEntries[] = configuration.getAppConfigurationEntry(serverSection); if (configurationEntries == null) { String errorMessage = "Could not find a 'Server' entry in" + " this configuration: Server cannot start."; LOG.error(errorMessage); throw new IOException(errorMessage); } credentials.clear(); for(AppConfigurationEntry entry: configurationEntries) { Map<String,?> options = entry.getOptions(); // 所有的用户名都被配置在以"user_"为前缀的属性名中 for(Map.Entry<String, ?> pair : options.entrySet()) { String key = pair.getKey(); if (key.startsWith(USER_PREFIX)) { String userName = key.substring(USER_PREFIX.length()); credentials.put(userName,(String)pair.getValue()); } } } }
总结
该机制虽然实现了基于用户名和密码的简单认证机制,但所有的用户名和密码信息都是静态配置的,无法支持用户的动态增加,这是该方案的最大软肋。
ZooKeeper安全认证机制:SSL
本文探讨ZooKeeper的SSL安全机制。默认情形下,ZooKeeper的网络通信是没有加密的,但ZooKeeper提供了SSL特性,目前仅应用在Client与Server端之间的交互(Server与Server之间的交互尚不支持),且RPC通信协议基于Netty时(ZooKeeper内置的NIO实现中不支持)。
SSL简介
SSL全称为Secure Socket Layer
,它是一种介于传输层和应用层的协议,它通过”握手协议”和“传输协议”来解决信息传输的安全问题,它可以被建立在任何可靠的传输层协议之上(例如TCP,但不能是UDP)。SSL协议主要提供如下三方面的能力:
- 信息的加密传播
- 校验机制,数据一旦被篡改,通信双方均会立刻发现
- 身份证书,防止身份被冒充
SSL的基本设计思想:
- Client向Server端索要”公钥“
- Client对获取的”公钥“进行校验
- 双方协商生成“会话密钥”
- 双方基于”会话密钥“进行信息交换
前3步称之为”握手阶段”,”握手阶段”采用”非对称加密“算法。
第4步称之为”传输阶段”,基于”对称加密“算法,”对称加密”算法的性能是远高于”非对称加密”算法的,因此,更适用于大数据量的传输加密。
如何使用
Client端配置
ZooKeeper Client通过配置如下系统属性来启用基于Netty的RPC通信层:
zookeeper.clientCnxnSocket=”org.apache.zookeeper.ClientCnxnSocketNetty”
Client需要设置如下参数来启用安全通信:
zookeeper.client.secure=true
设置了zookeeper.client.secure
属性为true
以后,意味着Client与Server之间只能通过"secureClientPort"
所指定的端口进行交互。
最后,需要配置KeyStore与TrustStore的相关系统属性:
zookeeper.ssl.keyStore.location=”/path/to/your/keystore”
zookeeper.ssl.keyStore.password=”keystore_password”
zookeeper.ssl.trustStore.location=”/path/to/your/truststore”
zookeeper.ssl.trustStore.password=”truststore_password”
Server端配置
ZooKeeper Server通过配置如下系统属性来启用Netty:
zookeeper.serverCnxnFactory=”org.apache.zookeeper.server.NettyServerCnxnFactory”
在”zoo.cfg”中配置”secureClientPort”端口值,该端口值与原来的”clientPort”端口值应该区别开:
secureClientPort=2281
最后也需要设置KeyStore与TrustStore的配置,与Client端配置类似。
配置示例
“bin/zkServer.sh”的配置示例如下:
export SERVER_JVMFLAGS=”
-Dzookeeper.serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory
-Dzookeeper.ssl.keyStore.location=/root/zookeeper/ssl/testKeyStore.jks
-Dzookeeper.ssl.keyStore.password=testpass
-Dzookeeper.ssl.trustStore.location=/root/zookeeper/ssl/testTrustStore.jks
-Dzookeeper.ssl.trustStore.password=testpass”
在 “zoo.cfg”中增加:
secureClientPort=2281
“bin/zkCli.sh”的配置为:
export CLIENT_JVMFLAGS=”
-Dzookeeper.clientCnxnSocket=org.apache.zookeeper.ClientCnxnSocketNetty
-Dzookeeper.client.secure=true
-Dzookeeper.ssl.keyStore.location=/root/zookeeper/ssl/testKeyStore.jks
-Dzookeeper.ssl.keyStore.password=testpass
-Dzookeeper.ssl.trustStore.location=/root/zookeeper/ssl/testTrustStore.jks
-Dzookeeper.ssl.trustStore.password=testpass”
X509AuthenticationProvider
默认情况下,SSL认证是由X509AuthenticationProvider
提供的,对应的schema为x509
。X509AuthenticationProvider
基于javax.net.ssl.X509KeyManager
与javax.net.ssl.X509TrustManager
提供Host
的证书认证机制。X509AuthenticationProvider仅仅当zookeeper.serverCnxnFactory
配置为NettyServerCnxnFactory
时才可使用,ZooKeeper内置的NIO实现类NIOServerCnxnFactory
并不支持SSL。
关键的配置项如下所示:
zookeeper.ssl.keyStore.location | KeyStore的路径 |
zookeeper.ssl.trustStore.location | TrustStore的路径 |
zookeeper.ssl.keyStore.password | KeyStore的访问密码 |
zookeeper.ssl.trustStore.password | TrustStore的访问密码 |
在KeyStore JKS文件中保存了Server的证书以及私钥信息,该证书需要由Client端信任,因此,该证书或CA(证书认证机构信息)也会被存储在Client端的TrustStore JKS文件中。同时,Server端的TrustStore JFS文件中存储了所信任的Client的证书/CA信息。
Client认证成功之后,会创建一个ZooKeeper Session,Client可以设置ACLs的schema为”x509″. “x509″使用Client认证成功后的X500 Principal作为ACL ID。 ACL信息中包含Client认证后的确切的X500 Principal名称。
关于X509与 X500:
X509: 一套数字证书体系标准
X500: 定义了一种区别命名规则,以命名树来确保用户名称的唯一性
与digest
认证类似,Server端可以配置一个X509的superUser
,对应的Property Key为:
zookeeper.X509AuthenticationProvider.superUser
superUser
可以绕过ACL配置从而拥有所有znodes的所有权限。
定制X509AuthenticationProvider
除了默认的X509AuthenticationProvider
以外,ZooKeeper允许自定义扩展实现X509的安全信任机制,尤其是Certificate Key Infrastructures不使用JKS时。
自定义实现X509AuthenticationProvider
应该遵循:
- 继承自
X509AuthenticationProvider
- KeyManager需要继承自
javax.net.ssl.X509ExtendedKeyManager
- TrustManager需要继承自
javax.net.ssl.X509ExtendedTrustManager
- 覆写
X509AuthenticationProvider
的getKeyManager
与getTrustManager
方法
这样,自定义的实现才会在SSLEngine中发挥作用。
自定义的AuthenticationProvider需要配置一个对应的schema
名称,并且通过系统属性"zookeeper.authProvider.[schema_name]"
来配置新定义的AuthenticationProvider实现类,这样在ProviderRegistry初始化时会自动加载。接下来,还需要设置系统属性"zookeeper.ssl.authProvider=[schema_name]"
,这样,新定义的AuthenticationProvider才可以被应用在安全认证中。
实现细节
NettyServerCnxnFactory
构造函数中初始化ChannelPipeline时调用初始化SSL的方法:NettyServerCnxnFactory#initSSL
方法的实现如下:CnxnChannelHandler#channelConnected
方法的定义如下:
当SslHandler中的handshake Future中的监听者被触发以后,由CertificateVerifier
来对证书的合法性进行校验,而CertificateVerifier
对证书进行校验的操作是由X509AuthenticationProvider
或者自定义的扩展实现类来完成:
Reference
- Client-Server Mutual Authentication
- ZOOKEEPER-938
- ZOOKEEPER-2125
- ZooKeeper SSL User Guide
- SSL/TLS协议运行机制的概述
http://www.nosqlnotes.com/technotes/zookeeper-acl/
http://www.nosqlnotes.com/technotes/zookeeper-digest-md5/
http://www.nosqlnotes.com/technotes/zookeeper-ssl/
相关推荐
针对zookeeper的安全漏洞,增加了对访问ip地址的限制。
下面小编就为大家分享一篇分布式服务Dubbo+Zookeeper安全认证实例,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
ZooKeeper 未授权访问【原理扫描】,zookeeper安全漏洞修复方法和操作步骤
12.1.4 zookeeper通知机制
ZooKeeper会话超时以及重连机制
zookeeper选举机制图,内讲述了zookeeper是如何选举出leader、fllower的
今天小编就为大家分享一篇关于Dubbo无法访问远程Zookeeper已注册服务的问题解决方案,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
1.4 zookeeper 通知机制 1.5 zookeeper 有哪些应用场景? 1.6 zk 的命名服务 1.7 zk 的配置管理服务 1.8 zk 的集群管理 1.9 zk 的分布式锁 2.0 zk 队列管理 2.1 zk 数据复制 2.2 zk 的工作原理 2.3 zk 是如何保证...
apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper-3.7.1 apache-zookeeper...
#Zookeeper的日志可以用LogFormatter查看 ##命令方式如下 java -classpath .:slf4j-api-1.7.2.jar:zookeeper-3.4.6.jar org.apache.zookeeper.server.LogFormatter /var/lib/zookeeper/version-2/log.1 ##window...
zookeeper 3.6.3 源码
zookeeper可视化工具
对于zookeeper 的机制原理有一个清晰翔实的梳理。
zookeeper的watcher机制,服务端和客户端你的源码流程,个人总结,个人复习使用
zookeeper linux 搭建流程,zookeeper linux 搭建流程zookeeper linux 搭建流程zookeeper linux 搭建流程。
zookeeper文档,开发必备,了解zookeeper工作原理,与机制
zookeeper 事件监听机制 zookeeper 集群搭建 一致性协议:zab协议 zookeeper的leader选举 observer角色及其配置 zookeeperAPI连接集群 zookeeper 开源客户端curator介绍 zookeeper四字监控命令 zookeeper图形化的...
【BAT必备】zookeeper面试题【BAT必备】zookeeper面试题【BAT必备】zookeeper面试题【BAT必备】zookeeper面试题【BAT必备】zookeeper面试题【BAT必备】zookeeper面试题【BAT必备】zookeeper面试题【BAT必备】...
详细可以参见reademe.txt和博客
dubbo2.6.0 + Zookeeper3.4.9 + Zookeeper3.8.0 + Zookeeper3.7.1