Druid集成Kerberos

背景:
  最近因业务方的需求,需要在Druid中集成Kerberos,用以权限控制,但是在集成过程中发现可参考的资料非常少,基本上可搜索到的都是关于Hdfs与kerberos集成相关的资料,所以自己集成过程中遇到了不少的坑.
  本文主要是对此次集成过程进行梳理和记录,同时也是作为自身的知识总结.本着程序猿的开源互助精神,也希望可以帮助到有类似需求的朋友

本文主要分为Kerberos内部集成与外部访问两大部分.

内部集成

一、前期准备

  1. Kerberos基本原理
      在开始集成前,最好先了解Kerberos的基本概念和原理,这样可以在安装集成过程中对繁杂的配置和操作做到心里有数,遇到问题也明白如何表达以及如何利用Google、度娘搜索.Kerberos基本原理可点击Kerberos传送门进行了解.

      总体而言,一个客户端使用kerberos获取服务时需要经过三个步骤:

    • 认证: 客户端向认证服务器(KDC)发送一条报文,并获取一个含时间戳的 Ticket-Granting Ticket(TGT).
    • 授权: 客户端使用 TGT 向认证服务器(KDC) Ticket-Granting Server(TGS)请求一个服务 Ticket
    • 服务请求: 客户端向目标服务器出示服务 Ticket ,以证实自己的合法性

      为此,Kerberos需要The Key Distribution Centers(KDC)来进行认证.KDC只有一个Master,可以带多个 slaves机器(也可以没有slaves).slaves机器仅进行普通验证.Mater上做的修改需要自动同步到 slaves.
    另外,KDC 需要一个 admin,来进行日常的管理操作.这个 admin 可以通过远程或者本地方式登录.

  2. Druid-Kerberos插件说明
      Druid本身已经支持与Kerberos的集成,具体是通过自带的Druid-Kerberos插件实现的.关于该插件的说明可参考Druid官网的文档说明,此处给出直达传送门

二、环境说明及代码调整

  1. 环境
      开始集成前先说明下工作环境,以免出现版本错配等问题
    • Druid: 使用的是当前的最新版本,即0.10.1.分布式的安装三个节点上,使用Ambari管理
    • Kerberos: 使用的是Kerbers5协议
    • 操作系统: CentOS 6.8
  2. 代码调整
      在刚开始根据Druid官网给出的配置进行集成时,通过查看coordinator的后台日志,发现其一直无法与historical节点通信,抛出以下异常:
Caused by: java.io.IOException: Login failure for druid/@SUGO.COM from keytab /opt/apps/kerberos/keytabs/druid.keytab
    at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:890) ~[?:?]
    at io.druid.security.kerberos.DruidKerberosUtil.authenticateIfRequired(DruidKerberosUtil.java:115) ~[?:?]
    ... 13 more
Caused by: javax.security.auth.login.LoginException: Unable to obtain password from user

    at com.sun.security.auth.module.Krb5LoginModule.promptForPass(Krb5LoginModule.java:897) ~[?:1.8.0_91]
    at com.sun.security.auth.module.Krb5LoginModule.attemptAuthentication(Krb5LoginModule.java:760) ~[?:1.8.0_91]
    at com.sun.security.auth.module.Krb5LoginModule.login(Krb5LoginModule.java:617) ~[?:1.8.0_91]
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:1.8.0_91]
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:1.8.0_91]
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:1.8.0_91]
    at java.lang.reflect.Method.invoke(Method.java:498) ~[?:1.8.0_91]
    at javax.security.auth.login.LoginContext.invoke(LoginContext.java:755) ~[?:1.8.0_91]
    at javax.security.auth.login.LoginContext.access$000(LoginContext.java:195) ~[?:1.8.0_91]
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:682) ~[?:1.8.0_91]
    at javax.security.auth.login.LoginContext$4.run(LoginContext.java:680) ~[?:1.8.0_91]
    at java.security.AccessController.doPrivileged(Native Method) ~[?:1.8.0_91]
    at javax.security.auth.login.LoginContext.invokePriv(LoginContext.java:680) ~[?:1.8.0_91]
    at javax.security.auth.login.LoginContext.login(LoginContext.java:587) ~[?:1.8.0_91]
    at org.apache.hadoop.security.UserGroupInformation.loginUserFromKeytab(UserGroupInformation.java:881) ~[?:?]
    at io.druid.security.kerberos.DruidKerberosUtil.authenticateIfRequired(DruidKerberosUtil.java:115) ~[?:?]

  配置:

druid.hadoop.security.kerberos.principal=druid@SUGO.COM
druid.hadoop.security.spnego.principal=HTTP/_HOST@SUGO.COM

  结合异常和配置,可以判断应该是因为keytab中没有包含druid@SUGO.COM的密码.那么按照官网给出的配置样例应该是想让我们创建一个druid@SUGO.COM公共用户供Druid节点内部交流使用,也就是在keytab中加入druid@SUGO.COM的密码.

  但是,再深入考虑一层,如果包含druid@SUGO.COM的keytab不小心被集群外的机器获取到,那么它可以利用这个kinint -k -t { keytap path } druid@SUGO.COM这个命令轻易侵入到Druid的内部交流中,这就与我们权限控制的初衷相违背了.所以我们参考第二个配置将druid.hadoop.security.kerberos.principal=druid@SUGO.COM改为druid.hadoop.security.kerberos.principal=druid/_HOST@SUGO.COM,也就是加上机器本身的hostname作为控制,以免上述情况的发生.

  相应的我们也就需要对解析这个参数的代码进行修改,具体的修改说明如下:

  • io.druid.security.kerberos.DruidKerberosUtil类中增加以下属性定义并初始化
  private static String hostname;    

  static{ //利用静态代码快初始化hostname
    try {
      hostname = InetAddress.getLocalHost().getHostName();   
    } catch (UnknownHostException e) {
      log.error("err ocurr when get the hostname",e);
    }
  }
  • io.druid.security.kerberos.DruidKerberosUtil类的authenticateIfRequired方法中增加一句代码

  public static void authenticateIfRequired(AuthenticationKerberosConfig config)
    throws IOException
  {
    String principal = config.getPrincipal();
    principal = principal.replace("_HOST",hostname);  //增加此句代码,将"_HOST"占位符替换为真正的hostname
    String keytab = config.getKeytab();
    if (!Strings.isNullOrEmpty(principal) && !Strings.isNullOrEmpty(keytab)) {
      Configuration conf = new Configuration();
      conf.set(CommonConfigurationKeysPublic.HADOOP_SECURITY_AUTHENTICATION, "kerberos");
      UserGroupInformation.setConfiguration(conf);
      try {
        if (UserGroupInformation.getCurrentUser().hasKerberosCredentials() == false
            || !UserGroupInformation.getCurrentUser().getUserName().equals(principal)) {
          log.info("trying to authenticate user [%s] with keytab [%s]", principal, keytab);
          UserGroupInformation.loginUserFromKeytab(principal, keytab);
        }
      }
      catch (IOException e) {
        throw new ISE(e, "Failed to authenticate user principal [%s] with keytab [%s]", principal, keytab);
      }
    }
  }

  最后将Druid代码重新编译打包,在集群中更新

三、集成Kerberos

  1. 安装Kerberos

    • 配置Druid集群机器的的hosts文件,确保集群每台机器hostname配置一致
$ cat /etc/hosts
127.0.0.1       localhost
192.168.0.223 dev223.sugo.net
192.168.0.224 dev224.sugo.net
192.168.0.225 dev225.sugo.net

注意:hostname请使用小写,要不然在集成Kerberos时会出现一些错误.

  • 安装kdc服务
    在Druid集群机器中选择一台机器作为kdc server,本例选择dev224.sugo.net.
$ yum install krb5-server krb5-libs krb5-auth-dialog krb5-workstation  -y
  • 安装devel和workstation服务
    在Druid集群的所有机器上安装devel和workstation服务
$ ssh dev223.sugo.net "yum install krb5-devel krb5-workstation -y"
$ ssh dev224.sugo.net "yum install krb5-devel krb5-workstation -y"
$ ssh dev225.sugo.net "yum install krb5-devel krb5-workstation -y"
  • 修改配置文件

    kdc 服务器涉及到三个配置文件:
    /etc/krb5.conf
    /var/kerberos/krb5kdc/kdc.conf
    /var/kerberos/krb5kdc/kadm5.acl


1.1. 配置Kerberos的一种方法是编辑配置文件 /etc/krb5.conf,默认安装的文件中包含多个示例项.
$ cat /etc/krb5.conf
[logging]
default = FILE:/var/log/krb5libs.log
kdc = FILE:/var/log/krb5kdc.log
admin_server = FILE:/var/log/kadmind.log

[libdefaults]
default_realm = SUGO.COM
dns_lookup_realm = false
dns_lookup_kdc = false
ticket_lifetime = 24h
renew_lifetime = 7d
forwardable = true
renewable = true
udp_preference_limit = 1
default_tgs_enctypes = arcfour-hmac
default_tkt_enctypes = arcfour-hmac

[realms]
SUGO.COM = {
kdc = dev224.sugo.net:88
admin_server = dev224.sugo.net:749
}

[domain_realm]
.sugo.com = SUGO.COM
sugo.com = SUGO.COM

[kdc]
profile=/var/kerberos/krb5kdc/kdc.conf
 配置说明: - `[logging]`: 表示 server 端的日志的打印位置 - `[libdefaults]`:每种连接的默认配置,需要注意以下几个关键的小配置:
参数名意义
default_realm设置 Kerberos 应用程序的默认领域。如果有多个领域,只需向 [realms] 节添加其他的语句
udp_preference_limit设置禁止使用udp
ticket_lifetime表明凭证生效的时限,一般为24小时
renew_lifetime表明凭证最长可以被延期的时限,一般为一个礼拜.当凭证过期之后,对安全认证的服务的后续访问则会失败
- `[realms]`:列举使用的 realm
参数名意义
kdc表要 kdc 的位置,格式是 机器:端口
admin_server代表 admin 的位置,格式是 机器:端口

krb5.conf文件包含 Kerberos 的配置信息。例如,KDC 的位置,Kerbero 的 admin 和 realms 等.需要所有使用 Kerberos 的机器上的配置文件都同步.这里仅列举本例需要的基本配置。详细介绍参考:krb5conf

 **1.2**. 修改kdc server(即`dev224.sugo.net`)的 `/var/kerberos/krb5kdc/kdc.conf`
$ cat /var/kerberos/krb5kdc/kdc.conf
[kdcdefaults]
 kdc_ports = 88
 kdc_tcp_ports = 88

[realms]
 SUGO.COM = {
  acl_file = /var/kerberos/krb5kdc/kadm5.acl
  dict_file = /usr/share/dict/words
  admin_keytab = /var/kerberos/krb5kdc/kadm5.keytab
  supported_enctypes = aes256-cts:normal aes128-cts:normal des3-hmac-sha1:normal arcfour-hmac:normal des-hmac-sha1:normal des-cbc-md5:normal des-cbc-crc:normal
 }
 配置说明:
参数名意义
SUGO.COM设定的realm,名字随意. Kerberos可以支持多个 realm,会增加复杂度.大小写敏感,一般为了识别使用全部大写.这个 realm 跟机器的host没有大关系
acl_file标注了 admin 的用户权限,需要用户自己创建. 文件格式是:Kerberos_principal permissions [target_principal] [restrictions]
dict_file不允许设为密码的字符串集所在的文件位置
admin_keytabKDC 进行校验的 keytab
supported_enctypes支持的校验方式

关于AES-256加密:
  对于使用centos5.6及以上的系统,默认使用aes256来加密的. 而JAVA使用aes256验证方式需要安装额外的Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy File这个jar包,这就需要集群中的所有节点上安装JCE. 请根据本地环境的JDK版本下载相应的JCE资源.本例使用的是JCE8
  下载的文件是一个 zip 包,解开后将里面的local_policy.jarUS_export_policy.jar两个文件放到下面的目录中:$JAVA_HOME/jre/lib/security,最后要注意文件的读权限

 **1.3**. 修改kdc server(即`dev224.sugo.net`)的 `/var/kerberos/krb5kdc/kadm5.acl`   Kerberos使用`kadm5.acl`对自身创建的数据库进行权限控制, 同时也用它控制哪些主体可以操作其他主体,具体的配置可以参考[这里](http://web.mit.edu/~kerberos/krb5-devel/doc/admin/conf_files/kadm5_acl.html),本例的配置如下:
$ cat /var/kerberos/krb5kdc/kadm5.acl
*/admin@SUGO.COM    *

* 代表赋予所有权限

 **1.4**. 同步配置文件   将kdc中的`/etc/krb5.conf`拷贝到集群中其他服务器即可
$ scp /etc/krb5.conf dev223.sugo.net:/etc/krb5.conf
$ scp /etc/krb5.conf dev225.sugo.net:/etc/krb5.conf
 **1.5**. 创建数据库   在 `dev224.sugo.net`上运行初始化数据库命令.其中 `-r` 指定对应 realm.
$ kdb5_util create -r SUGO.COM -s
  该命令会在 `/var/kerberos/krb5kdc/` 目录下创建 principal 数据库,如果遇到数据库已经存在的提示,可以把`/var/kerberos/krb5kdc/`目录下的 principal 的相关文件都删除掉.默认的数据库名字都是 principal.可以使用`-d`指定数据库名字  **1.6**. 启动服务并设置开机启动   在 `dev224.sugo.net`上运行: - 启动服务
$ service krb5kdc start
$ service kadmin start
  • 设置开机启动
$ chkconfig krb5kdc on
$ chkconfig kadmin on

1.7. 创建 kerberos 管理员
  在 dev224.sugo.net上使用 kadmin.local 命令创建管理员账户

$ kadmin.local -q "addprinc root/admin"    //手动输入两次密码

注意:这个命令要求有访问kdc服务器的 root权限

1.8. 测试kerberos
  以下内容仅仅是为了测试,你可以直接跳到下部分内容,以下命令均在dev224.sugo.net上执行.

  • 查看principals
$ kadmin.local -q "list_principals"
  • 添加一个新的principal
$ kadmin.local -q "addprinc user1"
Authenticating as principal HTTP/admin@SUGO.COM with password.
WARNING: no policy specified for user1@SUGO.COM; defaulting to no policy
Enter password for principal "user1@SUGO.COM": 
Re-enter password for principal "user1@SUGO.COM": 
Principal "user1@SUGO.COM" created.
  • 删除principal
kadmin.local -q "delprinc user1"
Authenticating as principal HTTP/admin@SUGO.COM with password.
Are you sure you want to delete the principal "user1@SUGO.COM"? (yes/no): yes
Principal "user1@SUGO.COM" deleted.
Make sure that you have removed this principal from all ACLs before reusing.
  • ticket测试
    kadmin.local -q "addprinc test"创建一个测试用户,密码设为test
$ kinit test     //通过kinit命令获取test用户的ticket
Password for test@SUGO.COM:   //输入test用户密码
$ klist    //查看当前获取到的ticket
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: test@SUGO.COM

Valid starting       Expires              Service principal
2017-11-22T20:12:12  2017-11-23T20:12:12  krbtgt/SUGO.COM@SUGO.COM
    renew until 2017-11-22T20:12:12
$ kdestroy   //销毁该test用户的 ticket

2 . Druid集成Kerberos

2.1. 为Druid创建用户
  根据Druid官网给出的关于Druid-Kerberos插件配置:

druid.hadoop.security.kerberos.principal=druid/_HOST@SUGO.COM
druid.hadoop.security.spnego.principal=HTTP/_HOST@SUGO.COM

  Druid内部交流需要用到druid/_HOST@SUGO.COMHTTP/_HOST@SUGO.COM用户,因此我们需要为集群中的每台机器创建以上两个用户,在 dev224.sugo.net上执行以下命令:

kadmin.local -q "addprinc -randkey druid/dev223.sugo.net@SUGO.COM"
kadmin.local -q "addprinc -randkey druid/dev224.sugo.net@SUGO.COM"
kadmin.local -q "addprinc -randkey druid/dev225.sugo.net@SUGO.COM"
kadmin.local -q "addprinc -randkey HTTP/dev223.sugo.net@SUGO.COM"
kadmin.local -q "addprinc -randkey HTTP/dev224.sugo.net@SUGO.COM"
kadmin.local -q "addprinc -randkey HTTP/dev225.sugo.net@SUGO.COM"

-randkey标志没有为新principal设置密码,而是指示kadmin生成一个随机密钥。之所以在这里使用这个标志,是因为以上principal不需要用户交互,仅是Druid内部使用,同时也可以隔绝其它principal的访问,保证了Druid内部的安全性.

  创建完成后,查看:

$ kadmin.local -q "listprincs"

2.2. 创建keytab文件
  keytab是包含principals和加密principal key的文件. keytab文件对于每个host是唯一的,因为key中包含 hostname. keytab文件用于不需要人工交互和保存纯文本密码,实现到kerberos上验证一个主机上的principal.
因为服务器上可以访问keytab文件即可以以principal的身份通过kerberos的认证,所以,keytab文件应该被妥善保存,应该只有少数的用户可以访问.
  在dev224.sugo.net节点,即 KDC server 节点上执行下面命令为druid/_HOST@SUGO.COMHTTP/_HOST@SUGO.COM用户创建keytab文件:

$ cd /var/kerberos/krb5kdc/
$ kadmin.local -q "xst  -k druid-unmerged.keytab  druid/dev223.sugo.net@SUGO.COM"
$ kadmin.local -q "xst  -k druid-unmerged.keytab  druid/dev224.sugo.net@SUGO.COM"
$ kadmin.local -q "xst  -k druid-unmerged.keytab  druid/dev225.sugo.net@SUGO.COM"

$ kadmin.local -q "xst  -k HTTP.keytab  HTTP/dev223.sugo.net@SUGO.COM"
$ kadmin.local -q "xst  -k HTTP.keytab  HTTP/dev224.sugo.net@SUGO.COM"
$ kadmin.local -q "xst  -k HTTP.keytab  HTTP/dev225.sugo.net@SUGO.COM"

  这样,就会在/var/kerberos/krb5kdc/目录下生成druid-unmerged.keytabHTTP.keytab两个文件,接下来使用ktutil合并这两个文件为 druid.keytab

$ cd /var/kerberos/krb5kdc/

$ ktutil
ktutil: rkt druid-unmerged.keytab
ktutil: rkt HTTP.keytab
ktutil: wkt druid.keytab
ktutil: exit

  使用 klist 显示 druid.keytab 文件列表:

$ klist -ket  druid.keytab
Keytab name: FILE:druid.keytab
KVNO Timestamp         Principal
---- ----------------- --------------------------------------------------------
   2 11/16/17 09:41:21 druid/dev223.sugo.net@SUGO.COM (aes256-cts-hmac-sha1-96) 
   2 11/16/17 09:41:21 druid/dev223.sugo.net@SUGO.COM (aes128-cts-hmac-sha1-96) 
   2 11/16/17 09:41:21 druid/dev223.sugo.net@SUGO.COM (des3-cbc-sha1) 
   2 11/16/17 09:41:21 druid/dev223.sugo.net@SUGO.COM (arcfour-hmac) 
   2 11/16/17 09:41:22 druid/dev223.sugo.net@SUGO.COM (des-hmac-sha1) 
   2 11/16/17 09:41:22 druid/dev223.sugo.net@SUGO.COM (des-cbc-md5) 
   2 11/16/17 09:41:22 druid/dev224.sugo.net@SUGO.COM (aes256-cts-hmac-sha1-96) 
   2 11/16/17 09:41:22 druid/dev224.sugo.net@SUGO.COM (aes128-cts-hmac-sha1-96) 
   2 11/16/17 09:41:22 druid/dev224.sugo.net@SUGO.COM (des3-cbc-sha1) 
   2 11/16/17 09:41:22 druid/dev224.sugo.net@SUGO.COM (arcfour-hmac) 
   2 11/16/17 09:41:22 druid/dev224.sugo.net@SUGO.COM (des-hmac-sha1) 
   2 11/16/17 09:41:22 druid/dev224.sugo.net@SUGO.COM (des-cbc-md5) 
   2 11/16/17 09:41:22 druid/dev225.sugo.net@SUGO.COM (aes256-cts-hmac-sha1-96) 
   2 11/16/17 09:41:22 druid/dev225.sugo.net@SUGO.COM (aes128-cts-hmac-sha1-96) 
   2 11/16/17 09:41:22 druid/dev225.sugo.net@SUGO.COM (des3-cbc-sha1) 
   2 11/16/17 09:41:22 druid/dev225.sugo.net@SUGO.COM (arcfour-hmac) 
   2 11/16/17 09:41:22 druid/dev225.sugo.net@SUGO.COM (des-hmac-sha1) 
   2 11/16/17 09:41:22 druid/dev225.sugo.net@SUGO.COM (des-cbc-md5) 
   2 11/16/17 09:41:22 HTTP/dev223.sugo.net@SUGO.COM (aes256-cts-hmac-sha1-96) 
   2 11/16/17 09:41:22 HTTP/dev223.sugo.net@SUGO.COM (aes128-cts-hmac-sha1-96) 
   2 11/16/17 09:41:22 HTTP/dev223.sugo.net@SUGO.COM (des3-cbc-sha1) 
   2 11/16/17 09:41:22 HTTP/dev223.sugo.net@SUGO.COM (arcfour-hmac) 
   2 11/16/17 09:41:22 HTTP/dev223.sugo.net@SUGO.COM (des-hmac-sha1) 
   2 11/16/17 09:41:23 HTTP/dev223.sugo.net@SUGO.COM (des-cbc-md5) 
   2 11/16/17 09:41:23 HTTP/dev224.sugo.net@SUGO.COM (aes256-cts-hmac-sha1-96) 
   2 11/16/17 09:41:23 HTTP/dev224.sugo.net@SUGO.COM (aes128-cts-hmac-sha1-96) 
   2 11/16/17 09:41:23 HTTP/dev224.sugo.net@SUGO.COM (des3-cbc-sha1) 
   2 11/16/17 09:41:23 HTTP/dev224.sugo.net@SUGO.COM (arcfour-hmac) 
   2 11/16/17 09:41:23 HTTP/dev224.sugo.net@SUGO.COM (des-hmac-sha1) 
   2 11/16/17 09:41:23 HTTP/dev224.sugo.net@SUGO.COM (des-cbc-md5) 
   2 11/16/17 09:41:23 HTTP/dev225.sugo.net@SUGO.COM (aes256-cts-hmac-sha1-96) 
   2 11/16/17 09:41:23 HTTP/dev225.sugo.net@SUGO.COM (aes128-cts-hmac-sha1-96) 
   2 11/16/17 09:41:23 HTTP/dev225.sugo.net@SUGO.COM (des3-cbc-sha1) 
   2 11/16/17 09:41:23 HTTP/dev225.sugo.net@SUGO.COM (arcfour-hmac) 
   2 11/16/17 09:41:23 HTTP/dev225.sugo.net@SUGO.COM (des-hmac-sha1) 
   2 11/16/17 09:41:23 HTTP/dev225.sugo.net@SUGO.COM (des-cbc-md5) 

  验证是否正确合并了key,使用合并后的keytab,分别使用druid和HTTP principals来获取证书.

$ kinit -k -t druid.keytab druid/dev224.sugo.net@SUGO.COM
$ kinit -k -t druid.keytab HTTP/dev224.sugo.net@SUGO.COM

   如果出现错误:kinit: Key table entry not found while getting initial credentials,
则上面的合并有问题,重新执行前面的操作

2.3. 部署kerberos keytab文件
  拷贝dev224.sugo.net机器上的druid.keytab 文件到其他节点的 /opt/apps/kerberos/keytabs 目录

$ cd /var/kerberos/krb5kdc/

$ scp druid.keytab dev223.sugo.net:/opt/apps/kerberos/keytabs
$ scp druid.keytab dev225.sugo.net:/opt/apps/kerberos/keytabs

  设置权限,分别在 dev223.sugo.netdev224.sugo.netdev225.sugo.net 上执行:

$ chown druid:druid /opt/apps/kerberos/keytabs/hdfs.keytab
$ chmod 400 /opt/apps/kerberos/keytabs/hdfs.keytab

  由于拥有keytab相当于有了永久凭证,不需要提供密码(如果修改kdc中的principal的密码,则该keytab就会失效),所以其他用户如果对该文件有读权限,就可以冒充keytab中指定的用户身份访问druid,所以 keytab文件需要确保只对 owner 有读权限(0400)

2.4. 修改 Druid 配置文件
  修改Druid项目中distribution目录下的pom文件,在project.build.plugins.plugin.executions.execution..configuration.arguments 标签加入以下两行代码:

<argument>-c</argument>
<argument>io.druid.extensions:druid-kerberos</argument>

  在Druid的公共配置里,增加kerberos相关的配置:

druid.extensions.loadList
=["postgresql-metadata-storage", "druid-hdfs-storage", "druid-lucene-extensions", "druid-kerberos"]     //增加druid-kerberos插件
druid.hadoop.security.kerberos.keytab=/opt/apps/kerberos/keytabs/druid.keytab
druid.hadoop.security.kerberos.principal=druid/_HOST@SUGO.COM
druid.hadoop.security.spnego.keytab=/opt/apps/kerberos/keytabs/druid.keytab
druid.hadoop.security.spnego.principal=HTTP/_HOST@SUGO.COM

  将Druid项目重新编译打包,在集群中更新启动.在集群机器中查看各个服务的后台日志,若没有出现错误则说明集成成功.

外部访问

  外部访问主要是指集群外部机器通过命令行、浏览器、程序等方式访问Druid的相关资源.

三、Linux 访问

本例以Ubuntu 16.04.2 LTS作为例子讲述如何在Liunx下访问Druid资源

1.注册用户
  在kdc,即dev224.sugo.net上为要进行的外部客户端注册一个principal:

$ kadmin.local -q "addprinc cyz@SUGO.COM"
Authenticating as principal HTTP/admin@SUGO.COM with password.
WARNING: no policy specified for cyz@SUGO.COM; defaulting to no policy
Enter password for principal "cyz@SUGO.COM":   
Re-enter password for principal "cyz@SUGO.COM": 
Principal "cyz@SUGO.COM" created.

2.生成keytab文件
  在kdc上执行命令

$ cd /var/kerberos/krb5kdc/
$ kadmin.local -q "xst  -k cyz.keytab  cyz@SUGO.COM"

3.安装kerberos客户端
  在要访问的外部客户端命令行中输入以下命令:

$ sudo apt-get install krb5-user

在安装过程中可能会跳出些弹框,这是用于配置外部客户端的krb5.conf,这里不了解的话可以随意填一些东西以便安装继续进行,后面会有相应的操作覆盖这个krb5.conf

4.获取keytab和配置文件
- 将第2步在kdc上生成的cyz.keytab文件复制到外部客户端的/opt/apps/kerberos/keytabs目录

$ cd /var/kerberos/krb5kdc/
$ scp cyz.keytab root@{Client IP}:/opt/apps/kerberos/keytabs
  • 将kdc上的/etc/krb5.conf文件复制到外部客户端的/etc目录下:
$ scp /etc/krb5.conf root@{Client IP}:/etc

5 修改外部客户端的hosts文件
  在要访问的外部客户端中加入druid集群的域名解析:

$ cat /etc/hosts
127.0.0.1   localhost

192.168.0.225 dev225.sugo.net
192.168.0.223 dev223.sugo.net
192.168.0.224 dev224.sugo.net

6.获取ticket
  在要访问的外部客户端中利用kinit命令获取ticket,输入klist命令查看ticket:

$ kinit -k -t  ../kerberos/keytabs/cyz.keytab cyz@SUGO.COM
$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: cyz@SUGO.COM

Valid starting       Expires              Service principal
2017-11-28T11:23:18  2017-11-29T11:23:18  krbtgt/SUGO.COM@SUGO.COM
    renew until 2017-11-28T11:23:18

7.通过命令行访问druid接口
  通过curl命令的--negotiate参数,在外部客户端的命令行访问druid

$ curl --negotiate -u:cyz -X 'POST' -H 'Content-Type:application/json' -d @task-spec.json http://dev225.sugo.net:8090/druid/indexer/v1/task

8.通过浏览器访问druid接口
  经过多番测试,目前支持Firefox浏览器的访问,不支持Chrome浏览器

  • 在外部客户端打开Firefox浏览器,在地址栏输入about:config进入配置页面,搜索network.negotiate-auth.trusted-uris

  • 双击,输入"http://druid-coordinator-hostname:ui-port""http://druid-overlord-hostname:port", 以,隔开:

http://dev225.sugo.net:8090,http://dev225.sugo.net:8081

  在新开页面的地址栏输入http://dev225.sugo.net:8090http://dev225.sugo.net:8081访问druid资源

四、Windows 访问

本例以Win7操作系统作为例子讲述如何在Windows下访问Druid资源

  1. 注册用户、获取keytab文件
      参考Linux 访问中的第1、2步,在kdc上进行用户注册、生成keytab文件操作,并将keytab文件放置在客户端的C:\ProgramData\Kerberos\keytabs目录下,方便起见,本例依旧使用cyz@SUGO.COM用户和cyz.keytab文件

  2. 修改外部客户端的hosts文件
      在客户端中修改C:\Windows\System32\drivers\etc目录下的hosts文件,在文件中加入druid集群的域名解析

192.168.0.225 dev225.sugo.net
192.168.0.223 dev223.sugo.net
192.168.0.224 dev224.sugo.net

注意操作系统可能会提示hosts文件不可编辑,此时可以复制该文件内容并在其它目录另建一个hosts文件后粘贴,然后追加以上内容,把新的hosts文件复制到C:\Windows\System32\drivers\etc覆盖原来的hosts文件

3.安装Kerberos客户端
  在MIT Kerberos下载页面中根据自身的操作系统位数下载对应的安装包, 本例客户端下载的是kfw-4.1-amd64.msi.下载后直接双击,不用特意配置即可自动化安装完毕

4.修改外部客户端的Kerberos配置
- 修改外部客户端的krb5.ini文件
  Windows系统下的Kerberos配置文件是krb5.ini,不是krb5.conf, 在第三步的客户端安装完毕之后,该文件默认位于C:\ProgramData\MIT\Kerberos5目录下, 如果没有找到,可以在windows文件浏览器中搜索krb5.ini. 将kdc上的krb5.conf文件内容复制、覆盖到krb5.ini文件

  • 修改客户端的path变量
      在第三步的客户端安装完毕之后,它就自动的在 PATH 里面加上了自己的执行目录,默认是C:\Program Files\MIT\Kerberos\bin. 但是,有些机器本身安装的某些jdk里面也带了一些kinit, ktab, klist等软件,所以输入kinit 、klist的时候执行的可能是jdk里面的工具或者是C:\Windows\System32路径下的命令, 因此,应把kfw的path放在最前面,然后重启客户端.

5.获取ticket
  在客户端的dos窗口中利用kinit命令获取ticket,输入klist命令查看ticket:

C:\Users>kinit -k -t C:\ProgramData\Kerberos\keytabs cyz@SUGO.COM
C:\Users>klist 
Ticket cache: API:Initial default ccache
Default principal: cyz@SUGO.COM

Valid starting       Expires              Service principal
11/28/17 14:38:40  11/29/17 14:38:40  krbtgt/SUGO.COM@SUGO.COM
    renew until 11/28/17 14:38:40

也可以打开桌面的MIT Kerberos Ticket Manager快捷方式,点击Get Ticket按钮,在弹出窗口中手动输入cyz@SUGO.COM和对应密码获取ticket

6.通过浏览器访问druid接口
  在Windows系统下目前仅支持Firefox浏览器的访问,不支持命令行的curl命令以及Chrome浏览器的访问

  • 在外部客户端打开Firefox浏览器,在地址栏输入about:config进入配置页面

  • 在配置页面中逐个搜索配置以下属性:

network.negotiate-auth.trusted-uris = http://dev225.sugo.net:8090,http://dev225.sugo.net:8081
network.negotiate-auth.using-native-gsslib = false
network.negotiate-auth.gsslib = C:\Program Files\MIT\Kerberos\bin\gssapi32.dll
network.auth.use-sspi = false
network.negotiate-auth.allow-non-fqdn = true

  在新开页面的地址栏输入http://dev225.sugo.net:8090http://dev225.sugo.net:8081访问druid资源,如果没能成功访问,尝试重启Firefox浏览器再进行访问

五、程序访问

本例以Linux系统下使用HttpClient作为例子讲述如何编写程序访问Druid资源

  1. 注册用户、获取keytab文件
      参考Linux 访问中的第1、2步,在kdc上进行用户注册、生成keytab文件操作,并将keytab文件放置在客户端的/opt/apps/kerberos/keytabs目录下,方便起见本例依旧使用cyz@SUGO.COM用户和cyz.keytab文件

  2. 获取krb5.conf
      将kdc上的/etc/krb5.conf文件复制到外部客户端的/etc目录下:

$ scp /etc/krb5.conf root@{Client IP}:/etc

3.修改客户端的hosts文件
  参考Linux 访问中的第5步,在客户端中加入druid集群的域名解析

4.编写访问程序

  • 新建maven工程,pom文件依赖如下
  <properties>
        <httpclient.version>4.3.3</httpclient.version>
        <httpcore.version>4.3.3</httpcore.version>
    </properties>

    <dependencies>
        <!--httpclient-->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpclient</artifactId>
            <version>${httpclient.version}</version>
        </dependency>
        <!-- httpcore -->
        <dependency>
            <groupId>org.apache.httpcomponents</groupId>
            <artifactId>httpcore</artifactId>
            <version>${httpcore.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
    </dependencies>
  • 编写访问程序
import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.auth.AuthSchemeProvider;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.Credentials;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.config.Lookup;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.SPNegoSchemeFactory;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;

import javax.security.auth.Subject;
import javax.security.auth.kerberos.KerberosPrincipal;
import javax.security.auth.login.AppConfigurationEntry;
import javax.security.auth.login.Configuration;
import javax.security.auth.login.LoginContext;
import java.io.IOException;
import java.security.Principal;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

/**
 * Created by cyz on 2017/11/23
 * Update date:
 * Time: 18:33
 * Describle :
 * Result of Test:测试通过
 * Command:
 * Email: chenyuzhi459@gmail.com
 */
public class HttpClientWithAuth {

    public static String user ="cyz@SUGO.COM";
    public static String keytab="/opt/apps/kerberos/keytabs/cyz.keytab";
    public static String krb5Location="/etc/krb5.conf";
    public static String isDebugStr = "false";
    private String principal ;
    private String keyTabLocation ;

    public HttpClientWithAuth(String principal, String keyTabLocation, String krb5Location) {
        this.principal = principal;
        this.keyTabLocation = keyTabLocation;
        System.setProperty("java.security.krb5.conf", krb5Location);
        System.setProperty("sun.security.spnego.debug", isDebugStr);  //
        System.setProperty("sun.security.krb5.debug", isDebugStr);
    }

    //模拟curl命令使用kerberos认证
    private static HttpClient buildSpengoHttpClient() {
        HttpClientBuilder builder = HttpClientBuilder.create();
        Lookup<AuthSchemeProvider> authSchemeRegistry = RegistryBuilder.<AuthSchemeProvider>create().
                register(AuthSchemes.SPNEGO, new SPNegoSchemeFactory(true)).build();   //采用 SPNEGO 认证方案
            builder.setDefaultAuthSchemeRegistry(authSchemeRegistry);
        BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();      //kerberos的ticket提供者
        credentialsProvider.setCredentials(new AuthScope(null, -1, null), new Credentials() {
            @Override
            public Principal getUserPrincipal() {
                return null;
            }
            @Override
            public String getPassword() {
                return null;
            }
        });
        builder.setDefaultCredentialsProvider(credentialsProvider);
        CloseableHttpClient httpClient = builder.build();
        return httpClient;
    }

    //配置kerberos属性
    private  Configuration getKerberosConfig(){
        return new Configuration() {
            @SuppressWarnings("serial")
            @Override
            public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
                return new AppConfigurationEntry[] { new AppConfigurationEntry("com.sun.security.auth.module.Krb5LoginModule",
                        AppConfigurationEntry.LoginModuleControlFlag.REQUIRED, new HashMap<String, Object>() {
                    {
                        put("useTicketCache", "false");
                        put("useKeyTab", "true");
                        put("keyTab", keyTabLocation);
                        //Krb5 in GSS API needs to be refreshed so it does not throw the error
                        //Specified version of key is not available
                        put("refreshKrb5Config", "true");
                        put("principal", principal);
                        put("storeKey", "true");
                        put("doNotPrompt", "true");
                        put("isInitiator", "true");
                        put("debug", isDebugStr);
                    }
                }) };
            }
        };
    }

    //Http Get Method
    public HttpResponse callRestUrl(final String url,final String userId) {
        System.out.println(String.format("Calling KerberosHttpClient %s %s %s",this.principal, this.keyTabLocation, url));
        Set<Principal> princ = new HashSet<Principal>(1);
        princ.add(new KerberosPrincipal(userId));
        Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>());
        try {
            //认证模块:Krb5Login
            LoginContext lc = new LoginContext("Krb5Login", sub, null, getKerberosConfig());
            lc.login();
            Subject serviceSubject = lc.getSubject();
            return Subject.doAs(serviceSubject, new PrivilegedAction<HttpResponse>() {
                HttpResponse httpResponse = null;
                @Override
                public HttpResponse run() {
                    try {
                        HttpUriRequest request = new HttpGet(url);

                        HttpClient spnegoHttpClient = buildSpengoHttpClient();
                        httpResponse = spnegoHttpClient.execute(request);
                        return httpResponse;
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                    return httpResponse;
                }
            });
        } catch (Exception le) {
            le.printStackTrace();
        }
        return null;
    }

    //Http Post Method
    public HttpResponse postRestUrl(final String url,final String userId,final String params) {
        System.out.println(String.format("Calling KerberosHttpClient %s %s %s",this.principal, this.keyTabLocation, url));
        Set<Principal> princ = new HashSet<Principal>(1);
        princ.add(new KerberosPrincipal(userId));
        Subject sub = new Subject(false, princ, new HashSet<Object>(), new HashSet<Object>());
        try {
            //认证模块:Krb5Login
            LoginContext lc = new LoginContext("Krb5Login", sub, null, getKerberosConfig());
            lc.login();
            Subject serviceSubject = lc.getSubject();
            return Subject.doAs(serviceSubject, new PrivilegedAction<HttpResponse>() {
                HttpResponse httpResponse = null;
                @Override
                public HttpResponse run() {
                    try {
                        StringEntity entity = new StringEntity(params,"UTF-8");
                        entity.setContentEncoding("UTF-8");
                        entity.setContentType("application/json");
                        HttpPost httpPost = new HttpPost(url);
                        httpPost.setEntity(entity);

                        HttpClient spnegoHttpClient = buildSpengoHttpClient();
                        httpResponse = spnegoHttpClient.execute(httpPost);
                        return httpResponse;
                    } catch (IOException ioe) {
                        ioe.printStackTrace();
                    }
                    return httpResponse;
                }
            });
        } catch (Exception le) {
            le.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) throws UnsupportedOperationException, IOException {

        HttpClientWithAuth restTest = new HttpClientWithAuth(user,keytab,krb5Location);

        String supervisorUrl = "http://dev225.sugo.net:8090/druid/indexer/v1/supervisor";

        String params = "{\n" +
                "  \"queryType\":\"lucene_timeBoundary\",\n" +
                "  \"dataSource\":\"druid-test002\",\n" +
                "  \"context\":null,\n" +
                "  \"intervals\":\"1000/3000\",\n" +
                "  \"bound\":null\n" +
                "}";
        String brokerUrl = "http://dev225.sugo.net:8082/druid/v2?pretty";

        HttpResponse response = restTest.postRestUrl(brokerUrl,user,params);  //查询数据源的maxTime,minTime
        printResponse(response);

        response = restTest.callRestUrl(supervisorUrl,user);    //获取正在运行的supervisor
        printResponse(response);
    }

    public static void printResponse(HttpResponse response) throws IOException {
        System.out.println("Status code " + response.getStatusLine().getStatusCode());
        System.out.println("返回值:\n"+new String(IOUtils.toByteArray(response.getEntity().getContent()), "UTF-8"));
    }
}

程序运行结果:

Calling KerberosHttpClient cyz@SUGO.COM /opt/apps/kerberos/keytabs http://dev225.sugo.net:8082/druid/v2?pretty
Status code 200
返回值:
[ {
  "timestamp" : "2017-05-01T00:00:01.520Z",
  "result" : {
    "maxTime" : "2017-05-30T23:59:58.141Z",
    "minTime" : "2017-05-01T00:00:01.520Z"
  }
} ]
Calling KerberosHttpClient cyz@SUGO.COM /opt/apps/kerberos/keytabs http://dev225.sugo.net:8090/druid/indexer/v1/supervisor
Status code 200
返回值:
["test-cyz"]

六、总结

本文介绍了 druid 集成 kerberos 认证的过程,其中主要需要注意以下几点:
1. 配置 hosts,hostname 请使用小写.
2. 确保 kerberos 客户端和服务端连通
3. 替换 JRE 自带的 JCE jar 包
4. 设置keytab的权限
5. 启动服务前,先获取 ticket 再运行相关命令

  另外,在外部访问中,用的比较多的Chrome浏览器没有得到支持是本文的一个遗憾,如果有哪位朋友能解决这个问题的话,欢迎交流分享.

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值