如何递归检查特定用户是否具有对文件夹及其内容的读取权限?

Scu*_*ort 5 permissions recursive

我需要将一个用户和一个目录传递给一个脚本,并让它吐出该目录中用户具有读访问权限的文件夹/文件的列表。MS 有一个名为 AccessChk for Windows 的工具可以做到这一点,但在 Unix 端是否存在类似的东西?我发现一些代码可以为特定文件夹或文件执行此操作,但我需要它来遍历目录。

Sté*_*las 8

TL; 博士

find "$dir" ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -r'
Run Code Online (Sandbox Code Playgroud)

您需要询问系统用户是否具有读取权限。唯一可靠的方法是将有效 uid、有效 gid 和补充 gid 切换为用户的并使用access(R_OK)系统调用(即使这对某些系统/配置有一些限制)。

更长的故事

让我们考虑一下 $user 需要什么才能获得读取权限/foo/file.txt(假设没有/foo并且/foo/file.txt是符号链接)?

他需要:

  1. 搜索访问/(无需读取
  2. 搜索访问/foo(无需读取
  3. 读取权限/foo/file.txt

您已经可以看到,仅检查 的权限的方法file.txt不起作用,因为file.txt即使用户没有/或 的搜索权限,它们也可以说是可读的/foo

还有一种方法,如:

sudo -u "$user" find / -readable
Run Code Online (Sandbox Code Playgroud)

也不会工作,因为它不会报告用户没有读取访问权限的目录中的文件(因为无法列出其内容而find运行$user),即使他可以读取它们。

如果我们忘记 ACL 或其他安全措施(apparmor、SELinux...)而只关注传统的权限和所有权属性,要获得给定的(搜索或读取)权限,那已经相当复杂且难以用find.

你需要:

  • 如果文件归您所有,则您需要所有者的权限(或 uid 0)
  • 如果该文件不归您所有,但该组是您的一个,那么您需要该组的该权限(或 uid 0)。
  • 如果它不归您所有,也不属于您的任何组,则其他权限适用(除非您的 uid 为 0)。

find语法上,这里以 uid 1 和 gids 1 和 2 的用户为例,这将是:

find / -type d \
  \( \
    -user 1 \( -perm -u=x -o -prune \) -o \
    \( -group 1 -o -group 2 \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user 1 \( ! -perm -u=r -o -print \) -o \
    \( -group 1 -o -group 2 \) \( ! -perm -g=r -o -print \) -o \
    ! -perm -o=r -o -print
Run Code Online (Sandbox Code Playgroud)

那个修剪用户没有搜索权限的目录和其他类型的文件(符号链接被排除,因为它们不相关),检查读取访问权限。

或者对于$user从用户数据库中检索到的任意成员及其组成员身份:

groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
  \( \
    -user "$user" \( -perm -u=x -o -prune \) -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" \( ! -perm -u=r -o -print \) -o \
    \( -group $groups \) \( ! -perm -g=r -o -print \) -o \
    ! -perm -o=r -o -print
Run Code Online (Sandbox Code Playgroud)

这里最好的方法是以 root 身份下降树并检查每个文件的用户权限。

 find / ! -type l -exec sudo -u "$user" sh -c '
   for file do
     [ -r "$file" ] && printf "%s\n" "$file"
   done' sh {} +
Run Code Online (Sandbox Code Playgroud)

或与perl

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -l -0ne 'print if -r'
Run Code Online (Sandbox Code Playgroud)

或与zsh

files=(/**/*(D^@))
USERNAME=$user
for f ($files) {
  [ -r $f ] && print -r -- $f
}
Run Code Online (Sandbox Code Playgroud)

这些解决方案依赖于access(2)系统调用。那不是重现系统用来检查访问权限的算法,而是要求系统使用相同的算法(考虑权限、ACL...)进行检查,它会使用您尝试打开用于阅读的文件,因此是最接近可靠解决方案的方法。

现在,所有这些解决方案都试图识别用户可能打开以进行阅读的文件路径,这与用户可能能够阅读内容的路径不同。要回答这个更通用的问题,需要考虑以下几点:

  1. $user 可能没有读访问权限,/a/b/file但如果他拥有file(并且具有对 的搜索访问权限/a/b,并且他拥有对系统的 shell 访问权限),那么他将能够更改 的权限file并授予自己访问权限。
  2. 如果他拥有/a/b但没有搜索权限,则情况相同。
  3. $user 可能没有访问权限,/a/b/file因为他没有搜索访问权限/a/a/b,但该文件可能有一个硬链接,/b/c/file例如,在这种情况下,他可能能够/a/b/file通过其/b/c/file路径打开它来读取内容。
  4. bind-mounts相同。他可能没有对 的搜索访问权/a,但/a/b可能被绑定安装在 中/c,因此他可以file通过/c/file其他路径打开阅读。

找到$user能够读取的路径。要解决 1 或 2,我们不能再依赖access(2)系统调用了。我们可以调整我们的find -perm方法来假设对目录的搜索访问权限,或者在您是所有者后立即对文件进行读取访问权限:

groups=$(id -G "$user" | sed 's/ / -o -group /g'); IFS=" "
find / -type d \
  \( \
    -user "$user" -o \
    \( -group $groups \) \( -perm -g=x -o -prune \) -o \
    -perm -o=x -o -prune \
  \) ! -type d -o -type l -o \
    -user "$user" -print -o \
    \( -group $groups \) \( ! -perm -g=r -o -print \) -o \
    ! -perm -o=r -o -print
Run Code Online (Sandbox Code Playgroud)

我们可以通过记录设备和 inode 编号或 $user 具有读取权限的所有文件并报告具有这些 dev+inode 编号的所有文件路径来解决 3 和 4。这一次,我们可以使用更可靠的 access(2)基于方法:

就像是:

find / ! -type l -print0 |
  sudo -u "$user" perl -Mfiletest=access -0lne 'print 0+-r,$_' |
  perl -l -0ne '
    ($w,$p) = /(.)(.*)/;
    ($dev,$ino) = stat$p or next;
    $writable{"$dev,$ino"} = 1 if $w;
    push @{$p{"$dev,$ino"}}, $p;
    END {
      for $i (keys %writable) {
        for $p (@{$p{$i}}) {
          print $p;
        }
      }
    }'
Run Code Online (Sandbox Code Playgroud)

并将这两个解决方案与:

{ solution1; solution2
} | perl -l -0ne 'print unless $seen{$_}++'
Run Code Online (Sandbox Code Playgroud)

到目前为止,如果您已经阅读了所有内容,应该很清楚,其中至少一部分仅涉及权限和所有权,而不是可能授予或限制读取访问权限的其他功能(ACL、其他安全功能...)。当我们分几个阶段处理它时,如果在脚本运行时创建/删除/重命名文件/目录或修改它们的权限/所有权,其中一些信息可能是错误的,例如在拥有数百万个文件的繁忙文件服务器上.

便携性说明

所有这些代码都是标准的(POSIX,Unixt位),除了:

  • -print0是一个 GNU 扩展,现在也被其他一些实现支持。对于find缺乏支持的实现,您可以-exec printf '%s\0' {} +改为使用,并替换-exec sh -c 'exec find "$@" -print0' sh {} +-exec sh -c 'exec find "$@" -exec printf "%s\0" {\} +' sh {} +.
  • perl不是 POSIX 指定的命令,但广泛可用。您需要perl-5.6.0或以上为-Mfiletest=access.
  • zsh不是 POSIX 指定的命令。这zsh上面的代码应与zsh的-3(1995)及以上的工作。
  • sudo不是 POSIX 指定的命令。只要系统配置允许perl以给定用户身份运行,代码应该适用于任何版本。