hadoop集群交互用户的永久kerberos票证

gzszwxb4  于 2021-05-29  发布在  Hadoop
关注(0)|答案(3)|浏览(440)

我有一个hadoop集群,它使用公司的activedirectory作为kerberos领域。节点和最终用户linux工作站都是ubuntu16.04。它们使用powerbroker pbis加入到同一个域,因此工作站和网格节点之间的ssh登录是单点登录。最终用户从其工作站运行长时间运行的脚本,这些脚本反复使用ssh在集群上首先启动spark/yarn作业,然后跟踪其进度,这些脚本必须在夜间和周末运行,远远超过kerberos票证的10小时生存期。
我正在寻找一种方法来为用户安装永久的、服务风格的kerberos密钥表,从而免除他们处理kinit的需要。我理解这意味着任何以特定用户身份访问网格的shell用户都可以作为该用户进行身份验证。
我还注意到,使用密码执行非sso ssh登录会自动创建从登录时起有效的网络票证。如果可以为sso登录启用此行为,则可以解决我的问题。

frebpwbc

frebpwbc1#

您只需请求用户添加 --principal 以及 --keytab 为他们的工作争论不休。然后spark(实际上是Yarn)代码会自动为您续票。使用这种方法,我们有可以运行数周的作业。
参见示例https://spark.apache.org/docs/latest/security.html#yarn-模式
对于像spark streaming apps这样的长时间运行的应用程序来说,可以分别通过--principal和--keytab参数将principal和keytab传递给spark submit。传入的keytab将通过hadoop分布式缓存复制到运行application master的机器上(如果yarn配置了ssl并且启用了hdfs加密,则会安全地进行复制)。kerberos登录将使用这个主体和keytab定期更新,hdfs所需的委托令牌将定期生成,以便应用程序可以继续写入hdfs。
当yarn更新kerberos票证时,您可以在spark驱动程序日志中看到。

9jyewag0

9jyewag02#

如果您正在访问hive/hbase或任何其他需要kerberos票证的组件,那么在票证过期的情况下重新登录spark代码。您必须更新票证才能使用keytab,而不是依赖已经存在于缓存中的tgt。这是通过使用hadoop安全包中的usergroupinformation类来完成的。在spark作业中添加以下代码片段,以便长期运行-

val configuration = new Configuration
configuration.addResource("/etc/hadoop/conf/hdfs-site.xml")
UserGroupInformation.setConfiguration(configuration)

UserGroupInformation.getCurrentUser.setAuthenticationMethod(AuthenticationMethod.KERBEROS)
UserGroupInformation.loginUserFromKeytabAndReturnUGI(
  "hadoop.kerberos.principal", " path of hadoop.kerberos.keytab file")
  .doAs(new PrivilegedExceptionAction[Unit]() {
    @Override
    def run(): Unit = {
       //hbase/hive connection
      // logic

    }
  })

上面我们指定服务主体的名称和生成的keytab文件的路径。只要keytab是有效的,我们的程序将为所有操作使用所需的服务主体,不管运行程序的用户是否已经验证并接收到tgt。
如果除了spark没有其他组件访问权限,那么就不需要编写上述代码。只需在spark submit命令中提供keytab和principal。

spark-submit --master yarn-cluster --keytab "xxxxxx.keytab" --principal "svc-xxxx@xxxx.COM"  xxxx.jar
46qrfjad

46qrfjad3#

我接受了上面的建议,使用--keytab参数在提交给spark的网格节点上指定一个自定义keytab。我使用下面的脚本创建我自己的每用户keytab。它一直保持到用户更改密码。
注意,该脚本简化了kerberos领域与定义用户的dns域和ldap目录相同的假设。这适用于我的设置,小心使用你的。它还希望用户是该网格节点上的sudoers。更精细的脚本可能会将keytab的生成和安装分离开来。


# !/usr/bin/python2.7

from __future__ import print_function

import os
import sys
import stat
import getpass
import subprocess
import collections
import socket
import tempfile

def runSudo(cmd, pw):
    try:
        subprocess.check_call("echo '{}' | sudo -S -p '' {}".format(pw, cmd), shell = True)
        return True
    except subprocess.CalledProcessError:
        return False

def testPassword(pw):
    subprocess.check_call("sudo -k", shell = True)
    if not runSudo("true", pw):
        print("Incorrect password for user {}".format(getpass.getuser()), file = sys.stderr)
        sys.exit(os.EX_NOINPUT)    

class KeytabFile(object):
    def __init__(self, pw):
        self.userName = getpass.getuser()
        self.pw = pw
        self.targetPath = "/etc/security/keytabs/{}.headless.keytab".format(self.userName)
        self.tempFile = None

    KeytabEntry = collections.namedtuple("KeytabEntry", ("kvno", "principal", "encryption"))

    def LoadExistingKeytab(self):
        if not os.access(self.targetPath, os.R_OK):

            # Note: the assumption made here, that the Kerberos realm is same as the DNS domain,
            # may not hold in other setups
            domainName = ".".join(socket.getfqdn().split(".")[1:])

            encryptions = ("aes128-cts-hmac-sha1-96", "arcfour-hmac", "aes256-cts-hmac-sha1-96")
            return [
                self.KeytabEntry(0, "@".join( (self.userName, domainName)), encryption)
                    for encryption in encryptions ]

        def parseLine(keytabLine):
            tokens = keytabLine.strip().split(" ")
            return self.KeytabEntry(int(tokens[0]), tokens[1], tokens[2].strip("()"))

        cmd ="klist -ek {} | tail -n+4".format(self.targetPath)
        entryLines = subprocess.check_output(cmd, shell = True).splitlines()
        return map(parseLine, entryLines)

    class KtUtil(subprocess.Popen):
        def __init__(self):
            subprocess.Popen.__init__(self, "ktutil",
                stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr=subprocess.PIPE, shell = True)

        def SendLine(self, line, expectPrompt = True):
            self.stdin.write(bytes(line + "\n"))
            self.stdin.flush()
            if expectPrompt:
                self.stdout.readline()

        def Quit(self):
            self.SendLine("quit", False)
            rc = self.wait()
            if rc != 0:
                raise subprocess.CalledProcessError(rc, "ktutil")

    def InstallUpdatedKeytab(self):
        fd, tempKt = tempfile.mkstemp(suffix = ".keytab")
        os.close(fd)
        entries = self.LoadExistingKeytab()
        ktutil = self.KtUtil()
        for entry in entries:
            cmd = "add_entry -password -p {} -k {} -e {}".format(
                entry.principal, entry.kvno + 1, entry.encryption)

            ktutil.SendLine(cmd)
            ktutil.SendLine(self.pw)

        os.unlink(tempKt)
        ktutil.SendLine("write_kt {}".format(tempKt))
        ktutil.Quit()

        if not runSudo("mv {} {}".format(tempKt, self.targetPath), self.pw):
            os.unlink(tempKt)
            print("Failed to install the keytab to {}.".format(self.targetPath), file = sys.stderr)
            sys.exit(os.EX_CANTCREAT)

        os.chmod(self.targetPath, stat.S_IRUSR)
        # TODO: Also change group to 'hadoop'

if __name__ == '__main__':

    def main():
        userPass = getpass.getpass("Please enter your password: ")
        testPassword(userPass)
        kt = KeytabFile(userPass)
        kt.InstallUpdatedKeytab()

    main()

相关问题