在Kerberos环境中,每当需要访问一个新服务时都需要获取服务实例对应的TGS,这导致在大规模集群中如果需要频繁进行实例部署/回收时会对Kerberos的KDC造成很大的访问压力,因此Hadoop设计了成本大幅降低的Delegation Token机制来进行安全校验的短路绕过Kerberos同时又能保证整体的安全性

Delegation Token

原理

Delegation Token和HDFS

上图展示了Delegation Token能够生效的原理,左侧为应用/终端正常使用基于Kerberos认证HDFS的业务流程:

  • 在不使用Delegation Token的情况下,应用/终端用户在第一次访问每个NameNode实例时,都会进行Kerberos认证(如果能够使用启动进程的Kerberos principal只需要进行TGS校验即可,否则还需要进行AS_REQ认证),之后访问DataNode使用Block的Block Access Token即可
  • 在分布式任务(也就是可能需要频繁进行实例部署/回收的场景)时,如果应用持有有效的Delegation Token则可以直接通过Delegation Token证明自己的身份,不需要再通过任何Kerberos的认证流程(如果Delegation Token不存在或者失效则会降级为Kerberos或者抛出异常),从而减少了对Kerberos的访问

Delegation Token作为一个对Kerberos的补充和扩展,其生命周期主要分为:

  • 用户通过Kerberos认证与NameNode进行双向认证,保证双方已经处于一个安全的上下文中
  • 用户通过HDFS的ClientProtocol协议调用NameNode接口获取Delegation Token
  • 用户将Delegation Token提交给Yarn的ResourceManager
  • Yarn根据用户提交的请求部署新的实例,在部署过程中将Delegation Token分发给各实例
  • HDFS客户端在访问NameNode时优先通过Delegation Token进行SASL认证,失败则降级为标准Kerberos
  • 当Delegation Token即将失效时,Yarn会统一通过访问NameNode来更新Delegation Token然后分发到之前的节点中,以此来保证Delegation Token的有效性

Delegation Token的颁发和维护

Delegation Token自身的颁发和维护都依赖于HDFS的NameNode:

  • NameNode通过ClientProtocol提供getDelegationToken/renewDelegationToken/cancelDelegationToken三个接口分别提供Delegation Token的颁发、续约和撤销
  • DelegationTokenSecretManager是NameNode中Delegation Token的实际维护者,归属于特定的命名空间,相关的Token会通过NameNode的EditLog/FsImage机制来保证高可用

SASL握手

HDFS客户端和服务端之间的安全认证机制都依赖于SASL,所以不论是Kerberos或者Delegation Token作用的起点都在SASL的建立过程中:

  • 在HDFS客户端中,基于SASL保护的连接的建立主要有三个阶段(源码参考:org.apache.hadoop.security.SaslRpcClient#saslConnect):NEGOTIATE、CHALLENGE以及SUCCESS
  • 客户端首先会主动连接服务端建立socket连接,然后发送特定的固定的negotiateRequest,如果服务端支持simple验证会直接发送SUCCESS标记让客户端直接进入SUCCESS阶段,否则将会发送NEGOTIATE标记、服务端支持的认证方式(auth method)以及第一次challenge的token通知客户端进入NEGOTIATE阶段

以下为一个服务端返回的NEGOTIATE报文示例:

state:NEGOTIATE
auths:
  - method:"TOKEN"
    mechanism:"DIGEST-MD5"
    protocol:""
    serverId:"default"
    challenge:"
      realm=\"default\",
      nonce=\"RSRMhaQdSoMpYJWFTexUObsZ8QMvEUqoAbpFh+ys\",
      qop=\"auth\",
      charset=utf-8,
      algorithm=md5-sess"
  - method:"KERBEROS"
    mechanism:"GSSAPI"
    protocol:"nn"
    serverId:"mynamenode.example.com"
  • 客户端进入NEGOTIATE阶段后,会按照服务端返回的支持的认证协议进行轮询(参考源码:org.apache.hadoop.security.SaslRpcClient#selectSaslClient),开启Kerberos认证后服务端默认会返回Token(这里的Token就是Delegation Token)和Kerberos两种协议,其中Token排序是高于Kerberos,所以会优先尝试使用Delegation Token来进行协商来短路成本更高的Kerberos协议
  • 轮询协议(参考源码:org.apache.hadoop.security.SaslRpcClient#selectSaslClient)的方式主要是通过获取协议对应的认证信息(参考源码:org.apache.hadoop.security.SaslRpcClient#createSaslClient,例如Token协议下就会读取本地缓存的Delegation Token,参考源码org.apache.hadoop.security.SaslRpcClient#getServerToken),如果能够成功获取某个协议的认证信息,则会用这个认证信息对服务端发送的第一次challenge的token进行evaluateChallenge(参考接口:javax.security.sasl.SaslClient#evaluateChallenge,Delegation Token会使用DIGEST-MD5算法进行加密和验证)然后将结果返回给服务端
  • 服务端收到客户端发送的NEGOTIATE结果(包括协议和evaluateChallenge的结果)会进行校验以确保客户端持有的认证信息是有效的,如果校验成功,则会发送第二次challenge的token以及CHALLENGE标记通知客户端进入CHALLENGE状态

以下为服务端返回的CHALLENGE报文示例:

state: CHALLENGE
token: 0x050401ff000c000000000000575e85d601010000853b728d5268525a1386c19f
  • 客户端进入CHALLENGE后会直接再次对第二次收到的服务端发送的新的challenge token进行evaluateChallenge并返回给服务端
  • 服务端收到CHALLENGE的结果后会先进行校验,如果校验通过则直接发送SUCCESS给客户端,通知双方已经完成SASL的握手过程,SASL支持的socket连接已经建立完成

这里需要注意的是:

  • 如果端口的特权端口(privileged port,低于1023的端口号),即使服务端开启了Kerberos认证依然会支持simple协议,参考HDFS的配置项(参考文档 hdfs-default.xml):
    • dfs.data.transfer.protection
    • nfs.allow.insecure.ports

Yarn与Delegation Token

在上文中我们了解了Delegation Token的颁发、维护以及在HDFS是如何生效的,但是Yarn到底是如何来管理和分发这些token的呢?

DelegationInYarn

参考上图,在Yarn环境中,Delegation Token的生效机制主要由以下流程完成(图中KMS是Hadoop Key Management Server的缩写,主要用于维护HDFS的加密区的数据加解密的密钥管理,这里不涉及所以忽略):

  1. 如果客户端或者脚本(client)期望应用部署在Yarn环境中并且使用Delegation Token,会首先向NameNode申请Delegation Token并且拿到本地
  2. client将job提交给Yarn的Resource Manager(RM),以及在第1步中获取的Delegation Token
  3. RM拿到Delegation Token会立即执行一次renew操作来验证Delegation Token的有效性,随后将job和Delegation Token一起分发到各个Worker Node
  4. RM会在Delegation Token即将过期(默认的整个有效时间的90%)时会主动进行Delegation Token的renew和重新分发,并且在整个job执行完毕后向NameNode申请撤销Delegation Token

这其中比较神秘的就是Yarn到底是如何将Delegation Token分发给各个Worker Node的呢?通过阅读Yarn的源码我们发现其核心是一个叫做Localizer的机制:

  • Yarn给每个Worker Node中部署的应用都绑定了一个本地文件,其中有个文件主要用于保存各种token,当NM(Node Manager)启动Worker Node时,Worker Node容器会从这个文件中加载token并且绑定到UGI(UserGroupInformation)中(参考源码:org.apache.hadoop.yarn.server.nodemanager.containermanager.localizer.ContainerLocalizer#runLocalization)
  • 所以Yarn的RM在给NM(Node Manager)下发部署任务时,只要保证token能够下发给NM并且被绑定在Worker Node容器的特定路径即可

Flink与Yarn

Flink通过Kerberos安全认证与HDFS进行交互时所使用的方式和实现均来自于HDFS官方,在使用时需要关注的与Kerberos有关的配置主要有:

  • security.kerberos.login.use-ticket-cache:是否使用本地(主要为操作系统,针对脚本的认证)已有的认证信息
  • security.kerberos.login.keytab:用于Kerberos认证的keytab
  • security.kerberos.login.principal:用于Kerberos认证的principal

当Flink部署在Yarn中时,可以通过配置实现基于Kerberos的认证:

  • 当Flink有配置keytab路径时,Flink会将keytab上传到Hadoop集群的HDFS中,在运行中每个实例都会优先通过keytab单独进行Kerberos认证并将认证后的principal设置为UGI的currentUser
  • 当Flink没有配置keytab时,Flink会在启动job时申请Delegation Token并提交给Yarn的RM,后续Yarn则会按照上文的描述进行Delegation Token的验证、分发、续约、撤销

这部分具体的实现参考官方的./bin/yarn-session.sh,这个脚本会通过启动org.apache.flink.yarn.cli.FlinkYarnSessionCli最终调用org.apache.flink.yarn.YarnClusterDescriptor完成Flink在Yarn的部署,详情参考org.apache.flink.yarn.YarnClusterDescriptor#startAppMaster:

  1. 如果开启Kerberos认证并且有配置keytab路径,默认(可以通过yarn.security.kerberos.ship-local-keytab进行配置)则会通过org.apache.flink.yarn.YarnApplicationFileUploader#registerSingleLocalResource将keytab上传到HDFS中,作为job的一部分资源文件被分发给job的所有容器中
  2. 通过org.apache.flink.yarn.Utils#setTokensFor获取Hadoop的token,包括用于HDFS的Delegation Token和用于HBase的HBase Token,这些token都会被放在UGI的currentUser的tokens中
  3. 所有获得的token都会被写入到一个byteBuffer中并被设置到ContainerLaunchContext中
  4. ContainerLaunchContext设置完毕后会被设置到ApplicationSubmissionContext,最后这个ApplicationSubmissionContext会被提交给RM