开启kerberos后,hue管理hbase

在部署hue+hbase thrift的过程中,遇到问题很多,主要做以下总结.
1. 代理权限问题:
    代理过程中必须保证hue票据用户和hbase thrift票据用户都具有代理权限.
     如果hue票据用户缺少代理权限, thrift日志会提示如*** can not do as ***
     如果thrift票据用户缺少代理权限, thrift日志会提示重试超时, 并不会有明显的错误提示.
2. 认证及票据设置问题:
    这是hue+hbase thrift最难调试的问题, 相关文档对该问题的描述也不清楚.
    一般按照官方文档部署,在通过hue访问hbase表时,可能会遇到以下几个问题
          1. 缺少tgt票据, 这个时候一般是hue票据指定的用户有误, 需要仔细检查hue用户
          2. 页面提示401, 查看thrift日志,提示Caused by: KrbException: Specified version of key is not available.这个错误是因为在认证过程中发           生错误, 如果hue的日志中打印出authenticate_user(): Authorization header: Negotiate **** 这样的字样,说明client的认证是正确的, 则很             可能说明client在申请服务票据时,指定的服务名称有误. 在实际部署过程中反复的对相关配置做了检查, 没有发现错误, 所以尝试对hue的源码做分析.

源码分析
    hue代码结构
          apps          各个组件的操作函数
          desktop    界面相关功能, 同时包含部分基础依赖

    hbase组件代码入口为apps/hbase/src/hbase/api.py, 该文件包括获取集群信息, 安全设置, 提供抽象操作接口, 并通过调用apps/hbase/gen-py     下的thrift接口进行实际操作. 以getTableList为例

api.py
def getTableList(self, cluster):
  # 返回客户端, 实际为gen-py/HBase.py, 其为hbase-thrift客户端,
  client = self.connectCluster(cluster)
  return [{'name': name, 'enabled': client.isTableEnabled(name, doas=self.user.username)} for name in client.getTa
               bleNames(doas=self.user.username)]

def connectCluster(self, name):
  # 获取安全相关信息, 格式为
  #   {kerberos_principal_short_name': kerberos_principal_short_name(/前面的部分),
  #   'use_sasl': use_sasl(true)}
  #    特别注意, 这部分通过读取/etc/hbase/conf下的配置获得,如果配置有误将报错
  _security = self._get_security()
  # 获取hbase集群的信息(Cluster_name:host:port), 这部分会抛出json异常, 这不是个错误
  target = self.getCluster(name)
  client = thrift_util.get_client(...)
  return client

thrift_util.py
def get_client(klass, host, port, service_name, **kwargs):
  # 获得connection config, 简单赋值
  conf = ConnectionConfig(klass, host, port, service_name, **kwargs)
  # A wrapper for a SuperClient
  # SuperClient(object):  A wrapper for a Thrift Client that causes it to automatically reconnect on failure.
  # 这部分的调用链很长,主要是对连接进行包装, 包括错误重试,维护连接之类.最终返回
  # TODO 补充调用链
return PooledClient(conf)

# 部分代码
def connect_to_thrift(conf):
  if conf.transport_mode == 'http':
    mode = THttpClient(conf.http_url)
  if conf.transport_mode == 'http':
    if conf.use_sasl and conf.mechanism != 'PLAIN':
      mode.set_kerberos_auth()
    else:
      mode.set_basic_auth(conf.username, conf.password)



以上代码返回 apps/hbase/gen-py/Hbase.py Client类. Client内部包含一个thrift_.py/http_client对象,负责向hbase thrift发送请求.
整体上,Hbase.py负责生成相关调用的上下文,并通过调用http_client接口发送相关请求.http_client负责处理安全,错误处理等.

thrift_.py/http_client的实现相对比较简单,内部保存了一个rest/http_client对象,负责处理具体的请求.所以thrift与rest在底层实现上实际上公用了一套代码.以下以安全设置部分代码为例

thrift_.py/http_client
def set_kerberos_auth(self):
  self._client.set_kerberos_auth()

rest/http_client
def set_kerberos_auth(self):
  """Set up kerberos auth for the client, based on the current ticket."""
  self._session.auth = HTTPKerberosAuth(mutual_authentication=OPTIONAL)
  return self

requsets_kerberos/kerberos_.py
class HTTPKerberosAuth(AuthBase):
    """Attaches HTTP GSSAPI/Kerberos Authentication to the given Request
    object."""
    def __init__(self, mutual_authentication=REQUIRED, service="HTTP"):
        self.context = {}
        self.mutual_authentication = mutual_authentication
        self.pos = None
        self.service = service

def authenticate_user(self, response, **kwargs):
    """Handles user authentication with gssapi/kerberos"""

    auth_header = self.generate_request_header(response)
    if auth_header is None:
        # GSS Failure, return existing response
        return response

    log.debug("authenticate_user(): Authorization header: {0}".format(
        auth_header))
    response.request.headers['Authorization'] = auth_header

    # Consume the content so we can reuse the connection for the next
    # request.
    response.content
    response.raw.release_conn()

    _r = response.connection.send(response.request, **kwargs)
    _r.history.append(response)

    log.debug("authenticate_user(): returning {0}".format(_r))
    return _r

def generate_request_header(self, response):
    """
    Generates the GSSAPI authentication token with kerberos.
    If any GSSAPI step fails, return None.
    """
    host = urlparse(response.url).hostname

    try:
        # 这里在做实际认证时会进行拼接,具体拼接的位置还没找到,拼接结果为HTTP/hostname@REALM
        result, self.context[host] = kerberos.authGSSClientInit(
            "{0}@{1}".format(self.service, host))
    except kerberos.GSSError:
        log.exception("generate_request_header(): authGSSClientInit() failed:")
        return None
    if result < 1:
        log.error("generate_request_header(): authGSSClientInit() failed: "
                  "{0}".format(result))
        return None
    try:
        result = kerberos.authGSSClientStep(self.context[host],
                                            _negotiate_value(response))
    except kerberos.GSSError:
        log.exception("generate_request_header(): authGSSClientStep() failed:")
        return None
    if result < 0:
        log.error("generate_request_header(): authGSSClientStep() failed: "
                  "{0}".format(result))
        return None
    try:
        gss_response = kerberos.authGSSClientResponse(self.context[host])
    except kerberos.GSSError:
        log.exception("generate_request_header(): authGSSClientResponse() "
                  "failed:")
        return None
    return "Negotiate {0}".format(gss_response)

到这里,就已经发现问题的根源,hue在与thrift做认证过程中,默认指定server_name为HTTP,所以在配置thrift的票据时必须指定服务名为HTTP,不然将导致认证错误(response 401),目前可见的代码中没有可以对server_name做配置的地方.这应该算是hue的一个不大不小的bug,有趣的是这个问题hue官方是知道的,在官方的说明中说是thrift使用的keytab文件中必须包含HTTP,但是没有做进一步的说明,这个相当的坑.

thrift使用的spnego协议
http://www.ibm.com/support/knowledgecenter/SS9H2Y_7.5.0/com.ibm.dp.doc/spnego_protocol.html
https://msdn.microsoft.com/en-us/library/ms995329.aspx

你可能感兴趣的:(开启kerberos后,hue管理hbase)