欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ZooKeeper安全认证机制

程序员文章站 2022-03-18 11:30:29
...

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

    只有一个名为anyoneIdworld: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`的实现类:
ZooKeeper安全认证机制
            
    
    博客分类: 认证安全 认证安全zookeeper大数据 
每一个AuthenticationProvider实现类所关联的schema如下所示:

 

AuthenticationProvider实现类 schema
DigestAuthenticationProvider digest
IPAuthenticationProvider ip
SASLAuthenticationProvider sasl
X509AuthenticationProvider x509

当znode acl schema为world时,是不需要经任何AuthenticationProvider进行认证的,因此不需要任何实现类。
当znode acl schema为auth时,代表着需要对请求上下文中的认证信息进行校验,在ServerCnxnauthInfo中保存了所有的已认证成功的Id以及认证服务所关联的的schema,由该schema再去ProviderRegistry中查找所关联的AuthenticationProvider实现类来对认证信息进行校验。
除了上述已有的实现者以外,用户还可以自定义实现AuthenticationProvider。自定义的实现类,需要设置到System Properties中,对应的Property Key需以"zookeeper.authProvider."开头。另外,自定义的AuthenticationProviderschema名称不应与现有的重名,否则会覆盖现有的实现。

Reference

  1. Client-Server Mutual Authentication
  2. ZOOKEEPER-938

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;
}

关于如上源码的更多备注信息:

  1. Login阶段,已经配置了LoginModule为org.apache.zookeeper.server.auth.DigestLoginModule
  2. DigestLoginModule中在初始化时已经将Client配置的usernamepassword信息加载到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;
    }
  3. 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的基本设计思想:

  1. Client向Server端索要”公钥
  2. Client对获取的”公钥“进行校验
  3. 双方协商生成“会话密钥
  4. 双方基于”会话密钥“进行信息交换

前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为x509X509AuthenticationProvider基于javax.net.ssl.X509KeyManagerjavax.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
  • 覆写X509AuthenticationProvidergetKeyManagergetTrustManager方法

这样,自定义的实现才会在SSLEngine中发挥作用。
自定义的AuthenticationProvider需要配置一个对应的schema名称,并且通过系统属性"zookeeper.authProvider.[schema_name]"来配置新定义的AuthenticationProvider实现类,这样在ProviderRegistry初始化时会自动加载。接下来,还需要设置系统属性"zookeeper.ssl.authProvider=[schema_name]",这样,新定义的AuthenticationProvider才可以被应用在安全认证中。

实现细节

NettyServerCnxnFactory构造函数中初始化ChannelPipeline时调用初始化SSL的方法:
ZooKeeper安全认证机制
            
    
    博客分类: 认证安全 认证安全zookeeper大数据 
NettyServerCnxnFactory#initSSL方法的实现如下:
ZooKeeper安全认证机制
            
    
    博客分类: 认证安全 认证安全zookeeper大数据 
CnxnChannelHandler#channelConnected方法的定义如下:
ZooKeeper安全认证机制
            
    
    博客分类: 认证安全 认证安全zookeeper大数据 
当SslHandler中的handshake Future中的监听者被触发以后,由CertificateVerifier来对证书的合法性进行校验,而CertificateVerifier对证书进行校验的操作是由X509AuthenticationProvider或者自定义的扩展实现类来完成:
ZooKeeper安全认证机制
            
    
    博客分类: 认证安全 认证安全zookeeper大数据 

Reference

  1. Client-Server Mutual Authentication
  2. ZOOKEEPER-938
  3. ZOOKEEPER-2125
  4. ZooKeeper SSL User Guide
  5. SSL/TLS协议运行机制的概述

http://www.nosqlnotes.com/technotes/zookeeper-acl/

http://www.nosqlnotes.com/technotes/zookeeper-digest-md5/

http://www.nosqlnotes.com/technotes/zookeeper-ssl/