基本 SFTP 服务

基本的 SFTP 服务无需额外设置,它是 OpenSSH 服务器的内置功能,并且由子系统 sftp-server(8) 实现 SFTP 文件传输。详细内容请参阅 sftp-server(8) 的手册页。另一个选择是,子系统 internal-sftp 可以实现一个内进程的 SFTP 服务器,这样使用 ChrootDirectory 强制客户端使用不同的文件系统根目录的配置可能会更简单。

客户端,SFTP 的选项和技巧与常规的 SSH 客户端相同。然而,有些客户端选项可能需要使用 -o 参数指定完整的选项名。对于许多专用的图形 SFTP 客户端,可以使用常规的 URL 来指向目标。现在许多文件管理器都内置了 SFTP 支持。参见上文的“GUI 客户端”部分。

基本 SFTP

SFTP 提供了一个非常易于使用且易于配置的选项,用于访问远程系统。再次强调,常规的 SFTP 访问不需要对默认配置进行任何额外的更改。可以使用常规客户端,也可以使用像 sshfs(1) 这样的特殊客户端

自动化 SFTP

SFTP 上传或下载可以实现自动化。前提是使用基于密钥的身份验证。一旦密钥认证工作正常,就可以使用批处理文件通过 SFTP 执行操作。有关详细信息,请参阅 sftp(1) 中的批处理选项 -b

$ sftp -b /home/fred/cmds.batch -i /home/fred/.ssh/foo_key_rsa server.example.org:/home/fred/logs/

如果批处理文件名使用了破折号(-),则 SFTP 命令将从标准输入读取。

$ echo "put /var/log/foobar.log" | sftp -b - -i /home/fred/.ssh/foo_key_rsa server.example.org:/home/fred/logs/

可以发送多个 SFTP 命令,但在这种情况下,最好使用批处理文件模式。

$ echo -e "put /var/log/foobar.log\nput /var/log/munged.log" | sftp -b - -i /home/fred/.ssh/foo_key_rsa server.example.org:/home/fred/logs/

批处理文件模式在 cron 任务和脚本中非常有用。

仅 SFTP 帐户

通过在 sshd_config(5) 中使用 Match 指令,可以限制特定组的成员仅使用 SFTP 与服务器交互。

Subsystem sftp internal-sftp

Match Group sftp-only
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp

请注意,禁用 TCP 转发只有在用户也被禁止使用 shell 访问时才有助于提高安全性,因为原则上用户可以安装自己的转发工具。

有关 Match 中可用模式的更多信息,请参见 ssh_config(5) 中的 PATTERNS 部分。

使用 SFTP 进行 chroot 的常见需求

对于一个账户组来说,他们通常需要读取和写入服务器上的主目录文件,而几乎没有理由访问文件系统的其余部分。SFTP 提供了一个非常易于使用且易于配置的 chroot(改变根目录)功能。在某些情况下,仅将用户 chroot 到他们的主目录就足够了。然而,这并不是完全直接的,因为在大多数情况下,主目录并非由 root 拥有,并且至少允许某个用户进行写入。不过,由于 SFTP 的 chroot 需要目标目录和所有父目录由 root 拥有,并且不能由其他用户写入,这就带来了一些困难,但这是必须的。没有所有者限制的情况下,用户是可以逃脱 chroot 的。

解决这个限制的一种方法是,将主目录的所有权设置为 root,并将一些其他目录和文件放置在该目录下,这些文件和目录的所有权属于常规帐户,用户可以在其中写入。

Match Group sftp-only
    ChrootDirectory %h
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp

在这种情况下,root 用户将需要在目标目录中填充所需的文件和子目录,然后将这些文件和子目录的所有权更改为普通账户的所有权。

设置 SFTP-only 帐户的三种权限方式

有至少三种其他方法可以设置 chrooted SFTP-only 帐户的主目录权限。每种方法都有其优点和缺点,因此适用于某些特定情况,而不适用于其他情况。

选项 1: 自动设置起始目录

如果将各个主目录的所有权设置为 root 不切实际,则可以进行妥协。ChrootDirectory 可以指向 /home,而 /home 必须由 root 拥有,接着 ForceCommand 可以使用 -d 选项指定用户的主目录作为起始目录。

Match Group sftp-only
    ChrootDirectory /home/
    ForceCommand internal-sftp -d %u

在 chroot 内嵌套另一个目录是另一种方式。子目录将可以由普通账户写入,而 chroot 目标目录则不能。

Match Group sftp-only
    ChrootDirectory /home/%u
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp -d %u

如果需要隐藏主目录的内容,使其他用户不可见,可以使用 chmod(1)。例如,可以为 /home 设置权限为 0111,为主目录设置权限为 0750070007702770 等,记得检查组成员身份。

选项 2: 嵌套的主目录

另外,为了实现类似的效果,但提供更多的隔离,可以将 chroot 账户的主目录再嵌套一层。请注意以下目录的所有权和权限:

$ ls -lhd /home/ /home/*/ /home/*/*/
drwxr-xr-x  4 root  root  4.0K Aug  4 20:47 /home/
drwxr-x---  3 root  user1 4.0K Aug  4 20:47 /home/user1/
drwxr-x---  3 root  user2 4.0K Aug  4 20:47 /home/user2/
drwxr-x--- 14 user1 user1 4.0K Aug  4 20:47 /home/user1/user1/
drwxr-x--- 14 user2 user2 4.0K Aug  4 20:47 /home/user2/user2/

然后,ChrootDirectory 指令可以将用户锁定在他们主目录上级的目录中,而 ForceCommand 指令可以通过 -d 选项将用户放到他们自己的主目录中。一旦登录,他们只能看到自己的文件。这种安排还使得稍后添加 chroot shell 访问变得更容易,因为系统目录可以被添加到 chroot 中,而不会对其他账户可用。

另一个常见的情况是将访问限制到 Web 服务器的文档根目录或服务器根目录。如果每个站点在 /var/www/ 下都有自己的层次结构,例如 /var/www/site1/,那么可以像下面这样使用 chroot:

Match Group team1
    ChrootDirectory /var/www/
    ForceCommand internal-sftp -d site1

Match Group team2
    ChrootDirectory /var/www/
    ForceCommand internal-sftp -d site2

然后,站点目录可以对组可写,而父目录 /var/www/ 对非 root 用户仍然是只读的。

选项 3: 拆分主目录及其内容的所有权

如前所述,有几种方式可以处理 chrooted SFTP 服务。第三种方法是将目录的所有权设置为 root,属于另一个组,但该组不能写入。因此,在以下设置中,账户 fred 可以登录并像平常一样使用任何预先创建的子目录或文件,但不能向 chroot 目标本身添加任何内容。

$ ls -lhd /home/ /home/fred/ /home/fred/*
drwxr-xr-x  68 root root 4.0K Sep 4 15:40 /home/
drwxr-xr-x  21 root fred 4.0K Sep 4 15:41 /home/fred/
drwxr-xr-x   8 fred fred 4.0K Sep 4 15:44 /home/fred/Documents
drwxr-xr-x   9 fred fred 4.0K Sep 4 15:41 /home/fred/Music
drwxr-xr-x 145 fred fred 4.0K Sep 4 15:41 /home/fred/Pictures
drwxr-xr-x   5 fred fred 4.0K Sep 4 15:41 /home/fred/Videos
drwxr-xr-x  98 fred fred 4.0K Sep 4 15:41 /home/fred/WWW

对应的 ssh_config(5) 配置如下,其中 fred 账户属于 team1 组:

Match Group team1
    ChrootDirectory /home/%u
    ForceCommand internal-sftp

这种方法设置简单,但缺点是每当要向主目录中添加新目录或文件时,都需要系统管理员干预。即使新文件或目录的所有权被更改为普通账户,仍然需要 root 权限才能进行操作。

Umask

从 OpenSSH 5.4 开始,sftp-server(8) 可以设置 umask 来覆盖用户账户默认设置的 umask。内进程 SFTP 服务器 internal-sftp 接受与外部 SFTP 子系统相同的选项。

Subsystem sftp internal-sftp -u 0022

但需要记住,umask 只会限制权限,永远不会放宽权限。

较早版本的 OpenSSH 可以通过使用帮助脚本来实现相同的功能,但这会大大增加 chroot 目录的复杂性。该帮助脚本可以是常规脚本,也可以内嵌在配置文件中,但这两种方式在 chroot 监狱中都不容易工作。通常,获取支持 umask 配置的较新版本的 sshd(8) 会更简单。以下是一个内嵌的帮助脚本,用于 OpenSSH 5.3 及更早版本,它基于 gilles@ 的版本:

Subsystem sftp /bin/sh -c 'umask 0022; /usr/libexec/openssh/sftp-server'

无论哪种方式,这个 umask 仅在服务器端生效。客户端的原始文件权限通常会在计算服务器上的最终文件权限时被使用,但并不总是如此。这取决于客户端本身。大多数客户端会将文件权限传递给服务器,FileZilla 是一个显著的例外。因此,通常情况下,权限可以被收紧,但不能放宽。例如,在客户端上是模式 600 的文件,不会自动变成 664 或任何低于原始 600 的权限,除非客户端不转发权限,在这种情况下,只会使用服务器的 umask。所以对于大多数客户端,如果你希望上传的文件权限更宽松,可以在上传前在客户端上更改文件权限。

进一步限制 Chrooted SFTP

有几个常见的场景,可以进一步限制 chroot 访问。

Chrooted SFTP 访问共享目录

另一种常见的情况是将一组用户 chroot 到他们负责的 Web 服务器的不同层次。显而易见,位于监狱内的符号链接指向监狱外的文件系统部分对于 chrooted 用户是不可访问的。因此,如果存在特殊的访问组合,目录层次结构必须更仔细地规划。请参见前文的“仅 SFTP 帐户”部分。

在这些目录中,可能需要为多个组提供不同级别的访问权限。在这种情况下,可能需要使用访问控制列表(ACL)。

Chrooted SFTP 账户仅可从特定地址访问

可以进行更复杂的匹配。例如,可以允许一组用户使用 SFTP,但如果他们仅从特定地址或地址范围登录,则不能使用 shell 登录。如果他们从正确的地址登录,则只能使用 SFTP;如果他们尝试从其他地址登录,则会完全拒绝访问。两种条件(肯定匹配和否定匹配)都需要考虑。

Subsystem sftp internal-sftp

Match Group sftp-only, Address 192.0.2.10
    AllowTCPForwarding no
    X11Forwarding no
    ForceCommand internal-sftp
    ChrootDirectory  /home/servers/

Match Group sftp-only, Address *,!192.0.2.10
    DenyGroups sftp-only

请注意,对于否定匹配,必须先指定通配符,然后是要排除的地址或范围。注意空格的使用。类似的匹配也可以通过指定 CIDR 地址/掩码格式的地址范围来进行,例如 192.0.2.0/24。可以指定多个条件,只有当所有条件都满足时,随后的指令才会生效。

适用的第一个 Match 块会生效,因此在构建条件块时必须小心,使其符合所需的确切情况。另外,任何不符合 Match 条件块的情况将会使用通用配置设置。

可以使用 -T-C 选项进行测试,以测试特定的用户和源地址组合。有关更多选项,请参见调试服务器配置部分。

Chrooted SFTP 与日志记录

如果未使用内进程 SFTP 服务器 internal-sftp,则日志记录守护进程必须在 chroot 目录中建立一个套接字,以便 sftp-server(8) 子系统访问 /dev/log。详见日志记录部分。

Chrooted 登录 Shell

为交互式 shell 创建 chroot 监狱是很困难的。chroot 及其所有组件必须是 root 拥有的目录,且不能由任何其他用户或组写入。ChrootDirectory 必须包含支持用户会话所需的文件和目录。对于交互式会话,至少需要一个 shell,通常是 bash(1)ksh(1)sh(1),以及 /dev 中的基本设备节点,如 null(4)zero(4)stdin(4)stdout(4)stderr(4)arandom(4)tty(4) 等设备

。路径中可能包含以下标记,这些标记在运行时会被展开,一旦连接的用户经过身份验证:%% 被替换为字面意义的 %%h 被替换为正在验证的用户的主目录,%u 被替换为该用户的用户名。

sshfs(1) - 通过本地文件夹进行 SFTP 文件传输

另一种传输文件或远程使用文件的方式是使用 sshfs(1)。它是基于 SFTP 的用户空间文件系统客户端,利用服务器的 SFTP 子系统。它可以将远程服务器上的一个目录作为本地文件系统中的目录进行访问,任何程序都可以访问该目录。用户必须具有读写权限才能使用 sshfs(1) 挂载点。

以下命令会在主目录中创建挂载点 mountpoint(如果没有该目录的话)。然后,sshfs(1) 会挂载远程服务器。

$ test -d ~/mountpoint || mkdir --mode 700 ~/mountpoint
$ sshfs fred@server.example.org:. ~/mountpoint

读取或写入挂载点中的文件,实际上是将数据从远程系统传输到本地或从本地传输到远程。可以使用压缩来减少传输过程中消耗的带宽。如果网络连接存在带宽限制或按单位收费,压缩可能很重要。然而,如果速度是唯一的问题,压缩可能会使传输变慢,特别是当两端的处理器较为繁忙或性能不足时。唯一的办法是测试并查看哪种方法更快。可以使用 -C 选项启用压缩:

$ sshfs -C fred@server.example.org:. ~/mountpoint

或者尝试使用调试输出:

$ sshfs -o sshfs_debug fred@server.example.org:. /home/fred/mountpoint

通过 sshfs(1) 使用命名管道时,管道无法在远程系统上正常工作。使用 fusermount -u 来卸载这些远程目录并关闭 SFTP 会话。

使用 sshfs(1) 和密钥

ssh_command 选项用于传递参数给 ssh(1)。在这个例子中,它用于让 ssh(1) 指向用于身份验证的密钥,并将远程目录 /usr/src 本地挂载为 /home/fred/src

$ sshfs -o ssh_command="ssh -i /home/fred/.ssh/id_rsa" fred@server.example.org:/usr/src /home/fred/src/

如果密钥已经加载到代理中,那么 ssh(1) 会自动找到并使用该密钥,而不需要干预。

Last modified: Sunday, 19 January 2025, 9:46 PM