Flink作为新一代的大数据处理引擎,其批流一体化的设计与出色的流处理性能,在业界得到了很多头部公司的青睐。目前运行Flink的集群多采用Yarn进行资源管理,这是最成熟的方案。Yarn做为Hadoop生态系统中的工具之一,客户端通常需要经过Kerberos认证才能使用Yarn提交或管理任务。那么Flink任务是如何提交到带有Kerberos认证的Yarn集群的呢?我们先从Kerberos的原理开始说起,再说如何让Flink在带有Kerberos认证的Yarn集群上跑起来,知其然知其所以然。
在Hadoop1.0.0或者CDH3以前,Hadoop集群中的所有节点几乎就是裸奔的,主要存在以下安全问题:
为了解决这些问题,kerberos认证出现了,它实现的是机器级别的安全认证。

其原理是事先将集群中的机器添加到kerberos数据库中,在数据库中分别产生主机与各个节点的keytab,并将这些keytab分发到对应的节点上。通过这些keytab文件,节点可以从数据库中获得与目标节点通信的密钥防止身份被冒充。针对Hadoop集群可以解决两方面的认证
对于具体到用户粒度上的权限控制,如哪些用户可以提交某种类型的作业,哪些用户不能,目前Kerberos还没有实现,需要有专门的ACL模块进行把控。
Kerberos认证过程会涉及以下几个基本概念:
component1 / component2 @ REALM事先对集群中确定的机器由管理员手动添加到kerberos数据库中,在KDC上分别产生主机与各个节点的keytab(包含了host和对应节点的名字,还有他们之间的密钥——Master Key),并将这些keytab分发到对应的节点上。
1. AS Exchange
通过这个过程,KDC(确切地说是KDC中的Authentication Service)可以实现对Client身份的确认,并颁发给该Client一个TGT。具体过程如下:

Client向KDC的Authentication Service发送Authentication Service Request(KRB_AS_REQ), 为了确保KRB_AS_REQ仅限于自己和KDC知道,Client使用自己的Master Key对KRB_AS_REQ的内容进行加密。KRB_AS_REQ内容主要包括:
AS在接收到的KRB_AS_REQ后从Account Database中提取Client对应的Master Key对Pre-authentication data进行解密,如果是一个合法的Timestamp,则可以证明发送方的确是Client信息中声称的那个人。
验证通过之后,AS将一份Authentication Service Response(KRB_AS_REP)发送给请求方。KRB_AS_REQ主要包含两个部分:该Client的Master Key加密过的Session Key(SKDC-Client:Logon Session Key)和被自己(KDC)的Master Key加密的TGT。而TGT大体又包含以下的内容:
Client通过自己的Master Key对第一部分解密获得Session Key之后,利用TGT便可以进行Kerberos认证的下一步:TGS Exchange。
2. TGS Exchange
Client先向TGS发送Ticket Granting Service Request(KRB_TGS_REQ),其主要内容为:
TGS收到KRB_TGS_REQ后,先使用他自己的Master Key对TGT进行解密,从而获得Session Key。随后使用该Session Key解密Authenticator,通过比较Authenticator中的Client Info和Session Ticket中的Client Info从而实现对Client的验证。

验证通过向对方发送Ticket Granting Service Response(KRB_TGS_REP)。这个KRB_TGS_REP有两部分组成:使用Logon Session Key(SKDC-Client)加密过用于Client和Server认证的Session Key(SServer-Client)和使用Server的Master Key进行加密的Ticket。该Ticket大体包含以下一些内容:
Client收到KRB_TGS_REP,使用Logon Session Key(SKDC-Client)解密第一部分后获得Session Key(SServer-Client)。有了Session Key和Ticket,Client就可以和Server进行交互,这时无须KDC介入了。我们看看 Client是如何使用Ticket与Server怎样进行交互的。
3. CS(Client/Server )Exchange
先是Client向Server认证自己的身份。这个过程与TGS Exchange中认证的过程类似,Client创建用于证明自己就是Ticket的真正所有者的Authenticator,并使用上一步获得的Session Key(SServer-Client)进行加密,然后将它和Ticket一起作为Application Service Request(KRB_AP_REQ)发送给Server。
除了上述两项内容之外,KRB_AP_REQ还包含一个Flag用于表示Client是否需要进行双向验证(Mutual Authentication)。

Server接收到KRB_AP_REQ之后,通过自己的Master Key解密Ticket,从而获得Session Key(SServer-Client)。通过Session Key(SServer-Client)解密Authenticator,进而验证对方的身份。验证成功,让Client访问需要访问的资源,否则直接拒绝对方的请求。
对于需要进行双向验证,Server从Authenticator提取Timestamp,使用Session Key(SServer-Client)进行加密,并将其发送给Client用于Client验证Server的身份。
在客户端使用Kerberos认证来获取服务时,需要经过三个步骤:
其关键在于获取TGT,客户端有了它就可以申请访问服务。所以第一种方式就是使用
如果本地安装了Kerberos客户端,可以使用kinit命令来获取TGT,
kinit wanghuan70
Password for wanghuan70@IDC.XXX-GROUP.NET:kinit -kt wanghuan70.keytab wanghuan70使用klist命令可以查看获取到的tgt的详细信息,包括Client principal、Service principal、位置、有效期等;
$ klist
Ticket cache: FILE:/tmp/krb5cc_2124
Default principal: wanghuan70@IDC.XXX-GROUP.NET
Valid starting Expires Service principal
08/03/2017 09:31:52 08/11/2017 09:31:52 krbtgt/IDC.XXX- GROUP.NET@IDC.XXX-GROUP.NET
renew until 08/10/2017 09:31:52在Flink client上执行这一系列操作后,再在Flink配置文件flink-conf.yaml里面添加如下配置
security.kerberos.login.use-ticket-cache: true这时Flink客户端就可以像一般情况一样直接用command向Yarn集群提交任务了。但是tgt有一个有效期,通常是一周,过期了就无法使用了,所以这种方式不适合长期任务。这就有了第二种方式——使用keytab,先获取token,后台再启动一个进程定期刷新token。
这种方式时通过客户端将keytab提交到Hadoop集群,再通过YARN分发keytab给AM和其他 worker container,具体步骤如下
UserGroupInformation.loginUserFromKeytab()来重新认证。
任务运行结束后,集群中的keytab也会随container被清理掉。
使用这种方式的话,Flink客户端需要持有keytab文件,并且在Flink配置文件flink-conf.yaml里面添加如下配置
security.kerberos.login.keytab: /home/hadoop_runner/hadoop-3.2.1/etc/hadoop/krb5.keytab
security.kerberos.login.principal: superuser
security.kerberos.login.contexts: Client注意:Flink客户端进行Kerberos认证是在加载集群动态配置之前进行的,所以需要在flink-conf.yaml文件中位置principal与keytab。在命令行中添加这个配置参数,实际还是用客户端的用户名作为principal进行认证,会报找不到tgt的错误。
出于于安全考虑,很多时候我们希望客户端能以某一个hadoop用户的身份去运提交任务、访问hdfs文件,目前实现方式主要有以下几种
export HADOOP_USER_NAME=hdfs @Public
@Evolving
public static synchronized UserGroupInformation getLoginUser() throws IOException {
if (loginUser == null) {
loginUserFromSubject((Subject)null);
}
return loginUser;
}
@Public
@Evolving
public static synchronized void loginUserFromSubject(Subject subject) throws IOException {
ensureInitialized();
try {
if (subject == null) {
subject = new Subject();
}
LoginContext login = newLoginContext(authenticationMethod.getLoginAppName(), subject, new UserGroupInformation.HadoopConfiguration());
login.login();
UserGroupInformation realUser = new UserGroupInformation(subject);
realUser.setLogin(login);
realUser.setAuthenticationMethod(authenticationMethod);
realUser = new UserGroupInformation(login.getSubject());
String proxyUser = System.getenv("HADOOP_PROXY_USER");
if (proxyUser == null) {
proxyUser = System.getProperty("HADOOP_PROXY_USER");
}
loginUser = proxyUser == null ? realUser : createProxyUser(proxyUser, realUser);
String fileLocation = System.getenv("HADOOP_TOKEN_FILE_LOCATION");
if (fileLocation != null) {
Credentials cred = Credentials.readTokenStorageFile(new File(fileLocation), conf);
loginUser.addCredentials(cred);
}
loginUser.spawnAutoRenewalThreadForUserCreds();
} catch (LoginException var6) {
LOG.debug("failure to login", var6);
throw new IOException("failure to login", var6);
}
if (LOG.isDebugEnabled()) {
LOG.debug("UGI loginUser:" + loginUser);
}
}然而直接在客户端设置这个环境变量或者Java系统变量是不work的,因为实际访问Hadoop的Operator是在TaskManager中运行的,所以需要将这个变量传到运行tm的NodeManager中,可以通过在Flink客户端配置参数env.ssh.opts或者env.java.opts来实现。
env.java.opts: -DHADOOP_PROXY_USER=hdfs # 配置所有Flink进程的JVM启动参数
env.ssh.opts: export HADOOP_PROXY_USER=hdfs # 启动jm、tm、zookeeper等服务的额外命令也可以通过配置containerized.master.env.与containerized.taskmanager.env.来传递环境变量。
然而,在目前版本(Flink 1.10)中,如果配置了keytab文件与Principal,Flink在后续中始终会以该Principal的名义提交任务,即便配置了HADOOP_PROXY_USER也起不到效果。针对这个issue,Uber提出了Flink on Yarn Security的改进方案,其进展详见Flink-11271,在Flink 1.11中会得到实现。
来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/69918097/viewspace-3123069/ ,如需转载,请注明出处,否则将追究法律责任。