我有一个Hadoop集群,它使用公司的Active Directory作为Kerberos领域。节点和最终用户Linux工作站都是Ubuntu 16.04。它们使用PowerBroker PBIS加入同一域,因此工作站和网格节点之间的SSH登录是单点登录。最终用户从他们的工作站运行长时间运行的脚本,这些脚本重复使用SSH首先在集群上启动Spark / Yarn作业,然后跟踪他们的进度,这些进度必须在夜间运行并且在周末远远超过10小时Kerberos票证的生命周期。
我正在寻找一种方法为用户安装永久的,服务式的Kerberos keytabs,从而减轻他们处理 kinit 的需要。我理解这意味着任何有shell访问网格的人都可以作为特定用户进行身份验证。
我还注意到,使用密码执行非SSO SSH登录会自动创建从登录时起有效的网络票证。如果可以为SSO登录启用此行为,那将解决我的问题。
答案 0 :(得分:1)
如果您正在访问Hive / Hbase或任何其他需要kerberos票证的组件,请在票证过期时使您的火花代码重新登录。您必须更新票证才能使用keytab,而不是依赖于TGT已经存在于缓存中。这是通过使用Hadoop Security包中的UserGroupInformation类完成的。在火花作业中添加以下片段以便长时间运行 -
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文件的路径。只要该密钥表有效,我们的程序将对所有操作使用所需的服务主体,无论运行该程序的用户是否已经过身份验证并收到TGT。
如果除了spark之外没有其他组件访问,那么您不需要编写上面的代码。只需在spark submit命令中提供keytab和principal。
spark-submit --master yarn-cluster --keytab "xxxxxx.keytab" --principal "svc-xxxx@xxxx.COM" xxxx.jar
答案 1 :(得分:1)
您只需要让用户将--principal
和--keytab
参数添加到其Spark作业即可。然后Spark(实际上是YARN)代码将自动为您续订票证。使用这种方法,我们的工作可以运行数周。
例如参见https://spark.apache.org/docs/latest/security.html#yarn-mode
对于运行时间较长的应用程序(例如Spark Streaming应用程序)能够写入 HDFS,可以传递主体和密钥表以进行火花提交 分别通过--principal和--keytab参数。密钥表 传入的内容将被复制到运行应用程序的计算机上 通过Hadoop分布式缓存掌握(安全-如果YARN为 配置为启用SSL和HDFS加密)。 Kerberos 使用该主体和密钥表将定期更新登录名,并且 HDFS所需的委托令牌将定期生成 因此应用程序可以继续写入HDFS。
当Yarn更新Kerberos票证时,您可以在Spark驱动程序日志中看到。
答案 2 :(得分:0)
我接受了上面的建议,使用-keytab 参数在提交给Spark的网格节点上指定自定义密钥表。我使用以下脚本创建自己的每用户密钥表。一直保持到用户更改密码为止。
请注意,该脚本做出简化的假设,即Kerberos领域与定义用户的DNS域和LDAP目录相同。这适用于我的设置,请谨慎使用。它还希望用户在该网格节点上更加谨慎。更完善的脚本可能会将密钥表的生成和安装分开。
#!/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()