HDFS权限指南

概述

Hadoop分布式文件系统(HDFS)为文件和目录实现了一种权限模型,该模型与POSIX模型有许多相似之处。每个文件和目录都与一个所有者及一个用户组相关联。文件或目录对所有者用户、同组其他用户以及其他所有用户分别设置了不同的权限。对于文件而言,读取文件需要r权限,写入或追加文件需要w权限。对于目录而言,列出目录内容需要r权限,创建或删除文件/目录需要w权限,访问目录子项则需要x权限。

与POSIX模型不同,由于没有可执行文件的概念,因此文件没有setuid或setgid位。出于简化考虑,目录也没有setuid或setgid位。可以在目录上设置粘滞位(sticky bit),这将防止除超级用户、目录所有者或文件所有者之外的任何人删除或移动该目录中的文件。对文件设置粘滞位则无效。文件和目录的权限统称为其模式(mode)。通常我们会使用Unix惯例来表示和显示模式,包括在本描述中使用八进制数字。当创建文件或目录时,其所有者是客户端进程的用户身份,其所属组则是父目录的组(BSD规则)。

HDFS还提供了对POSIX ACL(访问控制列表)的可选支持,通过针对特定命名用户或命名组的更细粒度规则来增强文件权限。本文档后续部分将更详细地讨论ACL。

每个访问HDFS的客户端进程都有一个由用户名和组列表组成的双重身份标识。当HDFS需要对客户端进程访问的文件或目录foo进行权限检查时,

  • 如果用户名与foo的所有者匹配,则测试所有者权限;
  • 否则,如果foo的组与groups列表中的任何成员匹配,则测试组权限;
  • 否则将测试foo的其他权限。

如果权限检查失败,客户端操作将失败。

用户身份

从Hadoop 0.22版本开始,Hadoop支持两种不同的操作模式来确定用户身份,由hadoop.security.authentication属性指定:

  • 简单模式

    在此操作模式下,客户端进程的身份由主机操作系统确定。在类Unix系统上,用户名等同于`whoami`命令的输出。

  • kerberos

    在启用Kerberos认证的环境中,客户端进程的身份由其Kerberos凭据决定。例如,在Kerberos环境中,用户可以使用kinit工具获取Kerberos票据授予票据(TGT),并使用klist查看当前主体名称。当将Kerberos主体映射到HDFS用户名时,除主要部分外的所有组件都会被忽略。例如,主体todd/foobar@CORP.COMPANY.COM在HDFS中将作为简单用户名todd进行操作。

无论采用何种操作模式,用户身份验证机制都是HDFS外部的。HDFS本身不提供创建用户身份、建立组或处理用户凭据的功能。

组映射

一旦如上所述确定了用户名,组列表将由一个组映射服务确定,该服务由hadoop.security.group.mapping属性配置。详情请参阅Hadoop Groups Mapping

权限检查

每个HDFS操作都要求用户具备特定权限(READ、WRITE和EXECUTE的某种组合),这些权限通过文件所有权、组成员身份或其他权限授予。操作可能会在路径的多个组件上执行权限检查,而不仅仅是最终组件。此外,某些操作还依赖于对路径所有者的检查。

所有操作都需要遍历访问权限。遍历访问要求对路径中除最后一个组件外的所有现有组件拥有EXECUTE权限。例如,对于任何访问/foo/bar/baz的操作,调用者必须对//foo/foo/bar拥有EXECUTE权限。

下表描述了HDFS对路径的每个组件执行的权限检查。

  • 所有权: 是否检查调用者是否为路径的所有者。通常,更改所有权或权限元数据的操作要求调用者是所有者。
  • 父目录: 请求路径的上级目录。例如,对于路径 /foo/bar/baz,其父目录是 /foo/bar
  • 祖先路径:请求路径中最后一个存在的组件。例如,对于路径/foo/bar/baz,如果/foo/bar存在,则祖先路径是/foo/bar。如果/foo存在但/foo/bar不存在,则祖先路径是/foo
  • Final: 请求路径的最终组成部分。例如,对于路径 /foo/bar/baz,最终路径组成部分是 /foo/bar/baz
  • 子树: 对于目录路径,包含该目录本身及其所有子目录(递归包含)。例如路径/foo/bar/baz包含两个名为buzboo的子目录时,其子树包含/foo/bar/baz/foo/bar/baz/buz/foo/bar/baz/boo
操作 所有权 父级 祖先 最终 子树
append 不适用 不适用 写入 不适用
concat 否 [2] 写入 (sources) 不适用 读取 (sources), 写入 (destination) 不适用
create 不适用 写入 写入 [1] 不适用
createSnapshot YES N/A N/A N/A N/A
delete 否 [2] 写入 不适用 不适用 读取, 写入, 执行
deleteSnapshot 不适用 不适用 不适用 不适用
getAclStatus 不适用 不适用 不适用 不适用
getBlockLocations 不适用 不适用 读取 不适用
getContentSummary 不适用 不适用 不适用 读取, 执行
getFileInfo 不适用 不适用 不适用 不适用
getFileLinkInfo 不适用 不适用 不适用 不适用
getLinkTarget 不适用 不适用 不适用 不适用
getListing 不适用 不适用 读取, 执行 不适用
getSnapshotDiffReport 不适用 不适用 读取 读取
getStoragePolicy 不适用 不适用 读取 不适用
getXAttrs 不适用 不适用 读取 不适用
listXAttrs 执行 不适用 不适用 不适用
mkdirs 不适用 写入 不适用 不适用
modifyAclEntries 不适用 不适用 不适用 不适用
removeAcl 不适用 不适用 不适用 不适用
removeAclEntries YES N/A N/A N/A N/A
removeDefaultAcl YES N/A N/A N/A N/A
removeXAttr 否 [2] 不适用 不适用 写入 不适用
重命名 否 [2] 写入 (源) 写入 (目标) 不适用 不适用
renameSnapshot 不适用 不适用 不适用 不适用
setAcl YES N/A N/A N/A N/A
setOwner 是 [3] 不适用 不适用 不适用 不适用
setPermission 不适用 不适用 不适用 不适用
setReplication 不适用 不适用 写入 不适用
setStoragePolicy 不适用 不适用 写入 不适用
setTimes 不适用 不适用 写入 不适用
setXAttr 否 [2] 不适用 不适用 写入 不适用
truncate 不适用 不适用 写入 不适用

[1] 只有在调用使用覆盖选项且路径上已存在文件时,才需要对create操作的最终路径组件拥有WRITE写权限。

[2] 任何检查父目录WRITE权限的操作,如果设置了粘滞位(sticky bit),也会检查所有权。

[3] 调用setOwner来更改文件所有者需要HDFS超级用户权限。更改文件所属组不需要HDFS超级用户权限,但调用者必须是文件所有者且属于指定组的成员。

理解实现原理

每个文件或目录操作都会将完整路径名传递给NameNode,并且每次操作都会沿路径应用权限检查。客户端框架会隐式地将用户身份与NameNode的连接关联起来,从而减少对现有客户端API的更改需求。一直以来都存在这样的情况:当对文件的一个操作成功时,重复该操作可能会失败,因为文件或路径上的某个目录已不存在。例如,当客户端首次开始读取文件时,它会向NameNode发出第一个请求以发现文件第一个块的位置。为查找其他块而发出的第二个请求可能会失败。另一方面,删除文件并不会撤销已经知道文件块的客户端的访问权限。随着权限的增加,客户端对文件的访问可能会在请求之间被撤销。同样,更改权限并不会撤销已经知道文件块的客户端的访问权限。

文件系统API的变更

所有使用路径参数的方法在权限检查失败时都会抛出AccessControlException

新方法:

  • public FSDataOutputStream create(Path f, FsPermission permission, boolean overwrite, int bufferSize, short replication, long blockSize, Progressable progress) throws IOException;
  • public boolean mkdirs(Path f, FsPermission permission) throws IOException;
  • public void setPermission(Path p, FsPermission permission) throws IOException;
  • public void setOwner(Path p, String username, String groupname) throws IOException;
  • public FileStatus getFileStatus(Path f) throws IOException;还将返回与路径关联的用户、组和模式。

新文件或目录的模式受限于作为配置参数设置的umask。当使用现有的create(path, …)方法(不带权限参数)时,新文件的模式为0666 & ^umask。当使用新的create(path, permission, …)方法(带权限参数P)时,新文件的模式为P & ^umask & 0666。当使用现有的mkdirs(path)方法(不带权限参数)创建新目录时,新目录的模式为0777 & ^umask。当使用新的mkdirs(path, permission)方法(带权限参数P)时,新目录的模式为P & ^umask & 0777

应用外壳的变更

新增操作:

  • chmod [-R] mode file ...

    只有文件所有者或超级用户才有权限更改文件的模式。

  • chgrp [-R] group file ...

    调用chgrp的用户必须属于指定的组并且是文件的所有者,或者是超级用户。

  • chown [-R] [owner][:[group]] file ...

    只有超级用户才能更改文件的所有者。

  • ls file ...

  • lsr file ...

    输出经过重新格式化,以显示所有者、组和权限模式。

超级用户

超级用户是指与NameNode进程本身具有相同身份的用户。简单来说,如果您启动了NameNode,那么您就是超级用户。超级用户可以执行任何操作,因为权限检查对超级用户永远不会失败。超级用户身份并非持久化存储的;当NameNode启动时,进程身份决定了当前谁是超级用户。HDFS的超级用户不一定是NameNode主机上的超级用户,也不要求所有集群都具有相同的超级用户。此外,实验者在个人工作站上运行HDFS时,无需任何配置即可自动成为该安装实例的超级用户。

此外,管理员可以通过配置参数指定一个特殊用户组。如果设置了该参数,该组的成员也将拥有超级用户权限。

Web服务器

默认情况下,Web服务器的身份是一个配置参数。也就是说,NameNode并不知晓真实用户的身份,但Web服务器会表现得如同它具有管理员选定用户的身份(用户和组)。除非选定的身份与超级用户匹配,否则Web服务器可能无法访问部分命名空间。

ACLs (访问控制列表)

除了传统的POSIX权限模型外,HDFS还支持POSIX ACL(访问控制列表)。ACL有助于实现与用户和组自然组织结构不同的权限需求。ACL提供了一种方式,可以为特定的命名用户或命名组设置不同的权限,而不仅限于文件所有者和文件所属组。

默认情况下,ACL支持功能是启用的,NameNode允许创建ACL。要禁用ACL支持功能,请在NameNode配置中将dfs.namenode.acls.enabled设置为false。

ACL由一组ACL条目组成。每个ACL条目指定特定用户或组,并为该特定用户或组授予或拒绝读取、写入和执行权限。例如:

   user::rw-
   user:bruce:rwx                  #effective:r--
   group::r-x                      #effective:r--
   group:sales:rwx                 #effective:r--
   mask::r--
   other::r--

ACL条目由类型、可选名称和权限字符串组成。出于显示目的,使用“:”作为各字段之间的分隔符。在此示例ACL中,文件所有者具有读写权限,文件组具有读执行权限,其他用户具有读权限。到目前为止,这相当于将文件的权限位设置为654。

此外,还有2个扩展ACL条目分别针对命名用户bruce和命名组sales,两者都被授予完全访问权限。mask是一个特殊的ACL条目,它会过滤授予所有命名用户条目、命名组条目以及未命名组条目的权限。在本示例中,mask仅具有读取权限,我们可以看到几个ACL条目的有效权限已相应地被过滤。

每个ACL都必须有一个掩码。如果用户在设置ACL时没有提供掩码,那么系统会自动通过计算所有会被掩码过滤的条目的权限并集来插入一个掩码。

对具有ACL的文件运行chmod实际上会更改掩码的权限。由于掩码充当过滤器,这实际上会限制所有扩展ACL条目的权限,而不仅仅是更改组条目,并可能遗漏其他扩展ACL条目。

该模型还区分了"访问ACL"(定义在权限检查期间执行的规则)和"默认ACL"(定义新创建的子文件或子目录自动继承的ACL条目)。例如:

   user::rwx
   group::r-x
   other::r-x
   default:user::rwx
   default:user:bruce:rwx          #effective:r-x
   default:group::r-x
   default:group:sales:rwx         #effective:r-x
   default:mask::r-x
   default:other::r-x

只有目录可以拥有默认ACL。当创建新文件或子目录时,它会自动将父级的默认ACL复制到自身的访问ACL中。新的子目录还会将其复制到自身的默认ACL中。通过这种方式,随着新子目录的创建,默认ACL将通过文件系统树的任意深层级向下复制。

新子项访问控制列表(ACL)中的确切权限值会受到mode参数的过滤影响。考虑到默认umask为022,新目录通常为755权限,新文件为644权限。mode参数会对未命名用户(文件所有者)、掩码和其他用户的复制权限值进行过滤。以这个特定的ACL示例为例,当使用755模式创建新子目录时,这种模式过滤对最终结果没有影响。但是,如果我们考虑用644模式创建文件,那么模式过滤会导致新文件的ACL为:未命名用户(文件所有者)获得读写权限,掩码获得读权限,其他用户获得读权限。这个掩码也意味着命名用户bruce和命名组sales的有效权限仅为读取。

请注意,复制操作发生在创建新文件或子目录时。后续对父级默认ACL的更改不会影响现有的子项。

默认ACL必须包含所有最低要求的ACL条目,包括未命名用户(文件所有者)、未命名组(文件组)和其他条目。如果用户在设置默认ACL时未提供这些条目之一,则会通过从访问ACL复制相应权限(如果没有访问ACL则从权限位复制)自动插入这些条目。默认ACL还必须包含掩码。如上所述,如果未指定掩码,则会通过计算所有受掩码过滤条目的权限并集来自动插入掩码。

请注意,对于给定的文件或目录,ACL条目数量不能无限增加。访问控制条目的最大数量为32条,默认条目也是32条,总计64条。

当考虑一个具有ACL的文件时,权限检查的算法变为:

  • 如果用户名与文件所有者匹配,则测试所有者权限;

  • 否则,如果用户名与某个命名用户条目中的名称匹配,则这些权限将通过掩码权限过滤后进行测试;

  • 否则,如果文件所属组与组列表中的任一成员匹配,并且这些权限经过掩码过滤后授予访问权限,则使用这些权限;

  • 否则,如果存在与组列表成员匹配的命名组条目,并且这些权限经过掩码过滤后授予访问权限,则使用这些权限;

  • 否则,如果文件组或任何命名组条目与组列表中的成员匹配,但访问未被任何这些权限授予,则访问被拒绝;

  • 否则将测试文件的其他权限。

最佳实践是依赖传统的权限位来实现大多数权限需求,并定义少量ACL来通过一些特殊规则增强权限位。与仅具有权限位的文件相比,具有ACL的文件会在NameNode中产生额外的内存开销。

ACLs 文件系统 API

新方法:

  • public void modifyAclEntries(Path path, List aclSpec) throws IOException;
  • public void removeAclEntries(Path path, List aclSpec) throws IOException;
  • public void public void removeDefaultAcl(Path path) throws IOException;
  • public void removeAcl(Path path) throws IOException;
  • public void setAcl(Path path, List aclSpec) throws IOException;
  • public AclStatus getAclStatus(Path path) throws IOException;

ACLs Shell 命令

  • hdfs dfs -getfacl [-R]

    显示文件和目录的访问控制列表(ACLs)。如果目录有默认ACL,则getfacl也会显示默认ACL。

  • hdfs dfs -setfacl [-R] [-b |-k -m |-x ] |[--set ]

    设置文件和目录的访问控制列表(ACLs)。

  • hdfs dfs -ls

    ls命令的输出会在任何具有ACL的文件或目录的权限字符串后附加一个'+'字符。

    有关这些命令的完整说明,请参阅File System Shell文档。

配置参数

  • dfs.permissions.enabled = true

    如果设置为true,则启用此处描述的权限系统。如果为false,则关闭权限检查,但其他所有行为保持不变。从一个参数值切换到另一个不会改变文件或目录的模式、所有者或组别。无论权限开启与否,chmod、chgrp、chown和setfacl始终会检查权限。这些功能仅在权限上下文中才有用,因此不存在向后兼容性问题。此外,这允许管理员在开启常规权限检查之前可靠地设置所有者和权限。

  • dfs.web.ugi = webuser,webgroup

    web服务器使用的用户名。将其设置为超级用户名称将允许任何web客户端查看所有内容。更改为其他未使用的身份则只允许web客户端查看"其他"权限可见的内容。可以在逗号分隔的列表中添加其他组。

  • dfs.permissions.superusergroup = supergroup

    超级用户组的名称。

  • fs.permissions.umask-mode = 0022

    创建文件和目录时使用的umask值。对于配置文件,可以使用十进制值18。

  • dfs.cluster.administrators = ACL-for-admins

    以ACL形式指定的集群管理员。这控制了谁可以访问HDFS中的默认servlet等。

  • dfs.namenode.acls.enabled = true

    设置为true以启用HDFS ACL(访问控制列表)支持。默认情况下ACL是启用的。当ACL被禁用时,NameNode会拒绝所有设置ACL的尝试。

  • dfs.namenode.posix.acl.inheritance.enabled

    设置为true以启用POSIX风格的ACL继承。默认启用。当启用此功能且创建请求来自兼容客户端时,NameNode会将父目录的默认ACL应用到创建模式,并忽略客户端的umask。如果未找到默认ACL,则会应用客户端的umask。