原文:http://hadoop.apache.org/docs/r2.6.4/hadoop-project-dist/hadoop-hdfs/HdfsPermissionsGuide.html
概述
HDFS实现了POSIX的文件目录的权限模型。每个文件和目录都关联一个owner和一个group。文件或者目录有独立分开的权限来对应到owner,对应到组成员,对应到其他用户。对于文件来说,读文件需要r权限,写或者append文件需要w权限。对于目录来说,展示目录里内容需要r权限,在目录里创建或删除文件或目录需要w权限,访问目录里的内容则需要x权限。
与POSIX模型相反的,HDFS没有对文件的setuid或者setgid位,因为HDFS文件是不可执行的。对于目录,为了简化,也不设置setuid或者setgid位。目录可以设置粘滞位,来保护目录内的文件不被其他非超级用户和文件owner的用户删除或移动。对于文件设置粘滞位是无效的。总的来说,文件或目录权限有其自身的模式。通常情况,对于权限模式是用Unix的习惯来表示的,包括使用八进制的描述。当一个文件或者目录创建时,它的owner就是客户端进程,它的group就是所在父目录的group(这是BSD的规矩)。
HDFS也提供对于POSIX ACL的可选的支持,以细粒度的规则来声明特定用户或组,以增强文件权限。ACL会在后面详细讨论。
每个访问HDFS的客户端进程都包含两部分id,一部分是用户名,另一部分是组列表。每当HDFS必须对一个客户端进程访问一个文件或目录foo做权限检查时,
- 如果用户名匹配了foo的owner,那么校验owner权限;
- 否则如果foo的group与组列表中的任意一个group匹配,那么校验group权限;
- 否则校验foo的其他权限。
如果权限检查失败,那么该次客户端操作失败。
用户身份标识
Hadoop 0.22版本里,支持两种不同的操作模式来决定用户身份,在属性hadoop.security.authentication里配置:
-
simple
在此模式下,客户端进程的id由宿主操作系统决定。在类Unix系统里,用户名称等同于命令`whoami`的结果。
-
kerberos
在基于Kerberos的模式下,客户端进程的id由Kerberos证书决定。举例来说,基于Kerberos的环境下,用户使用kinit来获取一个Kerberos ticket-granting-ticket (TGT) ,使用klist来决定当前的principle。当一个HDFS用户名和一个Kerberos principle匹配,除了第一个,所有其他的组件都被drop。举个例子,在HDFS里一个Kerberos的principle:todd/foobar@CORP.COMPANY.COM会变成一个简单的用户名todd。 不管使用哪种模式,对于HDFS自身来说用户识别机制属于外在内容。HDFS本身并没有对创建用户id,建立组,或者是处理用户证书做任何规定。
组映射
一旦用户名已经决定了,那么group列表会由组映射服务来决定,这个服务由hadoop.security.group.mapping配置属性确定。默认实现是org.apache.hadoop.security.JniBasedUnixGroupsMappingWithFallback
,前提是Java Native Interface (JNI)可用。如果JNI可用,实现方案会在hadoop内使用API来解析用户的group列表。如果JNI不可用,那么shell实现方案org.apache.hadoop.security.ShellBasedUnixGroupsMapping
会启用。这一方案利用bash -c groups
命令(Linux/Unix环境),或者net group
命令(Windows环境)来解析用户组列表。
一个替代的实现,是通过直接连接一个LDAP服务器来解析group列表,这个实现通过类org.apache.hadoop.security.LdapGroupsMapping
来完成。然而这个提供者使用起来有两个要求,被要求的组在LDAP上独占式驻留,并且要求不在Unix服务器上物化实现。关于配置组映射服务的更多信息参见Javadoc。
对于HDFS,用户到组的映射是通过NameNode完成的。因此,NameNode的主机系统配置决定了用户的组映射。
注意,HDFS以字符串的形式存储用户和组的文件和目录信息;不会像在Unix系统上一样对用户和组id做数字的转换。
理解实现
每个文件或者目录操作都会传完整的路径名给到NameNode,每个操作都会要求检查权限。客户端框架会隐式的关联NameNode连接和用户id,来减少改变现有客户端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;
会额外返回用户,组和与路径相关的权限mode。
新的文件或目录的mode由作为配置参数的umask来限制。当使用create(path, …)
方法(无权限参数)时,新文件的mode是0666 & ^umask
。当使用mkdirs(path)
方法(无权限参数)创建新目录时,新目录的mode是0777 & ^umask
。当使用新的mkdirs(path, permission)
方法(权限参数为P)时,新目录的mode是P & ^umask & 0777
。
应用程序shell的变更
新操作:
-
chmod [-R] mode file …
只有文件的owner或者超级用户允许来变更文件的权限mode。 -
chgrp [-R] group file …
调用chgrp
的用户必须属于对应的group并且是文件的owner,或者是超级用户。 -
chown [-R] [owner][:[group]] file …
只有超级用户能改变一个文件的owner。 ls file …
-
lsr file …
输出,并且输出被格式化为显示owner,group和权限mode。
超级用户
超级用户就是NameNode进程本身。宽松的条件下,如果你启动了NameNode,你就是超级用户。超级用户拥有最大权限,对于超级用户的权限检查永久不会失败。没有人是永久的超级用户,谁启动NameNode进程决定谁是超级用户。HDFS超级用户不一定是NameNode主机的超级用户,所有的集群也不必拥有同一个超级用户。同样的,如果一个在个人电脑上尝试性运行HDFS的人,也不需要任何配置就是该次运行的超级用户。
此外,对应某个group的管理员由一个配置参数来指定。如果设置了,那么该组的成员也将是超级用户。
web服务器
默认情况,web服务器的识别是一个配置参数。NameNode对于实际用户的标识并不知情,但是web服务器就像由管理员选定的用户一样来运行。除非被选择的id是超级用户,否则部分命名空间将不可访问。
ACLs (访问控制列表)
对于传统的POSIX权限模型,HDFS也支持POSIX的ACLs。ACLs对于实现不同于正常用户和组的组织层级权限需求非常有效。一条ACL提供了一种方法来为特定的用户或组设置不同的权限,不仅仅是文件的owner和文件的group。
默认情况,对于ACLs的支持是disabled,并且NameNode不允许创建ACLs。要开启ACLs,需要在NameNode的配置里设置dfs.namenode.acls.enabled为true。
一条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项包含了一个类型,一个可选的名字和一个权限字符串。为了显示的需要,':'用来作为分隔符。在这个例子里,文件的owner拥有读写权限,文件的group拥有读和执行权限,其他人拥有读权限。到此,这等价于设置文件的权限bit为654。
除此之外,还有两个扩展的ACL项,一个对应特定的用户bruce,一个对应特定的组sales,两者都被授予了完全访问权限。mask是一种特殊的ACL项,用来过滤授予特定用户或组,也包含非特定的组的权限。在此例中,mask只有读权限,对应的几个ACL项也被相应的过滤,只有读权限。
每条ACL必须有一个mask。如果用户不提供mask,那么系统会自动插入一条mask,该mask是通过union所有项的权限作为过滤项计算出来的。
对一个拥有ACL的文件运行chmod
会导致改变mask的权限。由于mask是一个过滤器,这有效的限制了所有扩展的ACL项,而不是仅仅改变group项和可能丢失的其他扩展ACL项。
该模型也区分“访问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,那么对于新的文件来说就是644,对于新目录就是755。mode参数过滤对于未指定的用户(文件owner),mask和其他用户的复制来的权限。使用这一特定的示例ACL,创建一个新的子目录,其权限mode是755,那么mode过滤器对于最后的结果不起作用。然而,如果我们假设创建一个文件mode是644,那mode过滤器会过滤新文件的ACL,导致对于未指定用户(文件owner)拥有读写权限,对于mask拥有读权限,对于其他用户拥有读权限。这个mask同样作用于已制定名字的bruce和sales,其权限也将是只读。
注意,ACL的复制发生在新目录或文件创建时。后续的对于父目录的默认ACL的变更不会影响已经存在的子文件或目录。
默认ACL必须拥有最小的ACL项集,包括未指定用户(文件owner),未指定group(文件group)和其他用户。如果用户没有提供上述这些项的设置,那么系统自动复制访问ACL中的对应项,或者如果没有访问ACL,就复制权限位。默认ACL必须有mask。如上所述,如果mask未声明,系统自动创建一个mask,此mask通过union所有项的权限过滤计算得出。
当一个文件拥有ACL时,对于权限的校验算法修改为:
- if用户名与文件owner匹配,那么检测owner权限;
- Else if用户名匹配指定的用户项,那么检测这些匹配的用户权限,这些权限先要经过mask过滤;
- Else if文件的group与group列表的任意成员匹配,并且经过mask过滤的权限授权访问,那么使用这些权限;
- Else if指定的group项匹配group列表中的任意成员,并且经过mask过滤的权限授权访问,那么使用这些权限;
- Else if文件group或者任意指定的group项与group列表中的任意成员匹配,但是没有被授权访问,那么访问拒绝;
- Otherwise校验文件其他用户的权限。
实现权限控制的最佳实践是利用传统的权限位,同时定义很小的一组ACLs来对可能的例外规则增强权限校验。带有ACL的文件权限校验相比只用权限位的文件会导致NameNode额外的内存消耗。
ACLs文件系统API
新方法:
public void modifyAclEntries(Path path, List<AclEntry> aclSpec) throws IOException;
public void removeAclEntries(Path path, List<AclEntry> 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<AclEntry> aclSpec) throws IOException;
public AclStatus getAclStatus(Path path) throws IOException;
ACLs shell命令
-
hdfs dfs -getfacl [-R] <path>
显示文件和目录的ACLs。如果一个目录包含默认ACL,那么getfacl会显示该默认ACL。 -
hdfs dfs -setfacl [-R] [-b|-k -m|-x <acl_spec> <path>]|[--set <acl_spec> <path>]
设置文件和目录的ACLs。 -
hdfs dfs -ls <args>
如果文件或目录包含ACL,ls的输出会附加一个'+'到权限字符串。
全部命令参见Hadoop文件系统shell。