如何允许多个 SFTP 用户使用 S3FS 和 OpenSSH?
一切正常,除了 SFTP 用户没有权限写入他们的 Chrooted 主目录:remote open("/some_file"): Permission denied
设置
我有一个运行 Amazon Linux 的 Amazon EC2 实例。我安装了 S3FS 并挂载了一个 S3 存储桶。我还配置了 OpenSSH 以允许 SFTP 用户访问已挂载的 S3 存储桶内的 Chrooted Home 目录/s3_mounted_folder/user_folder/
。我已成功在非 S3 挂载目录上使用 SFTP 连接。我已成功使用 S3 存储桶通过 SSH 在 EC2 实例上以 root 身份从 S3 创建和下载文件。我的 SFTP 用户可以成功地从他们的/s3_mounted_folder/user_folder/
目录中下载文件。问题是 SFTP 用户无法将put
文件放入 S3 挂载文件夹中。
问题...我认为
我只能使用相同的用户:组和相同的权限配置所有文件夹(/s3_mounted_folder/
和),因此,我无法授予用户写入其主目录( )的权限。如果我使用用户或组挂载存储桶并授予写入权限,则 OpenSSH SFTP 将不允许用户连接,因为它认为用户权限配置错误(例如:vs. )。/s3_mounted_folder/user_folder/
/s3_mounted_folder/user_folder/
drwxr-xr-x 1 root root
drwxrwxr-x 1 root usergroup
S3FS 命令
以下是在这两种模式下启动 S3FS 的两个不同命令(其中用户 501 和组 501 是 SFTP 用户和组):
root 用户权限(drwxr-xr-x 1 root root
):sudo s3fs nwd-sftp /sftp/ -o iam_role=sftp-server -o allow_other -o umask=022
sftp 用户权限(drwxrwxr-x 1 root usergroup
):sudo s3fs nwd-sftp /sftp/ -o iam_role=sftp-server -o allow_other -o umask=002 -o gid=501
在第二种情况下,用户理论上可以put
通过 SFTP 将文件放入他们的主目录中,但 SFTP 不允许他们连接,因为他们的 Chrooted 主目录对非 root 组具有写权限。
答案1
我有一个非常相似的设置,但使用的是 NFS 而不是 S3。我的解决方案是将 NFS 主目录挂载到主目录路径之外的挂载点,然后根据需要autofs
自动将用户主目录挂载到用户无权写入的顶级目录中/jail
。
相关配置:
sshd
Subsystem sftp internal-sftp -l DEBUG -u 002 -d %u
UsePAM yes
Match group usergroup
# This configuration section is part of the sftp-chroot project – https://github.com/mle86/sftp-chroot/
X11Forwarding no
AllowTcpForwarding no
ChrootDirectory /jail/%u # see /etc/auto.master.d/jails.autofs
ForceCommand internal-sftp -l DEBUG -u 002 # see sftp-server(8) for options
自动主控
/jail program:/etc/autofs-sftp-jails.sh --timeout=20
汽车之家
* -fstype=nfs4,rw,hard,intr,rsize=2048,wsize=2048,nosuid,nfsvers3 10.0.0.1:/nfsmount/&
autofs-sftp-jails.sh
#!/bin/sh
#
# This file is part of the sftp-chroot project – https://github.com/mle86/sftp-chroot/
#
# This autofs script will allow any local user's homedir to be mounted
# under /jail with a mountpoint named like the user, e.g.
# /jail/xyz → fake empty directory (root:root 0755)
# /jail/xyz/~xyz → /~xyz
#
# The base directory /jail will only be accessible for root.
# All mountpoints under /jail are therefore only usable as chroot base directories.
#
# Exit codes:
# - 0 Success. Has printed one autofs(5) map entry.
# - 1 Argument was not a valid username.
# - 2 User exists, but has no homedir entry.
# - 3 User's homedir does not exist (or is not a directory).
# - 4 User's homedir contains a symlink component.
# - 5 User's homedir contains a non-directory component (?!).
# - 6 User's homedir has too many components.
# - 7 User homedir component could not be resolved.
# - 8 User's homedir contains forbidden characters.
## Initialization: ############################################################
set -e # die on errors
username="$1"
PROGNAME="$0($username)"
MOUNT_TO=/jail # duplicated in auto.master.d/jails.autofs
## Helper functions: ##########################################################
# fail [exitStatus=1] errorMessage
# Exits the script with an error message and an optional exit status.
fail () {
local status=1
if [ -n "$2" ]; then
status="$1"
shift
fi
printf '%s: %s\n' "${PROGNAME:-$0}" "$*" >&2
exit $status
}
# user_homedir username
# Retrieves and prints a user's homedir.
# Exits with an error message and a non-zero status if
# "getent passwd $username" did not succeed, or
# the resulting passwd line did not contain a homedir part, or
# the homedir does not exist or is not actually a directory.
# The result is therefore guaranteed to be an existing directory.
user_homedir () {
local username="$1"
local uent=
uent="$(getent passwd -- "$username")" || fail 1 "user not found"
local homedir="$(printf '%s' "$uent" | cut -d':' -f6)"
[ -n "$homedir" ] || fail 2 "no homedir entry"
[ -d "$homedir" ] || fail 3 "homedir not found: $homedir"
printf '%s\n' "$homedir"
}
# remove_trailing_slashes string
# Removes any trailing slashes in the string, if there are any,
# and prints the result.
remove_trailing_slashes () {
printf '%s' "$1" | sed 's:/*$::'
}
# homedir_name_check homedir
# Checks its argument for dangerous characters.
homedir_name_check () {
local homedir="$1"
case "$homedir" in
*':'*) fail 8 "homedir with colon rejected: $homedir" ;;
*'*'*) fail 8 "homedir with asterisk rejected: $homedir" ;;
*'`'*) fail 8 "homedir with backtick rejected: $homedir" ;;
*'"'*|*"'"*) fail 8 "homedir with quote rejected: $homedir" ;;
*"\\") fail 8 "homedir with backslash rejected: $homedir" ;;
esac
true
}
# homedir_symlink_check homedir
# Checks that its argument's path components do not contain any symlinks
# and are all existing, real directories.
homedir_symlink_check () {
local homedir="$(remove_trailing_slashes "$1")"
# trailing slash has to go, or the -L test will RESOLVE the symlink instead of recognizing it!
local n_max=50
local stopdir='/'
local testdir="$homedir"
while [ -n "$testdir" ] && [ "$testdir" != "$stopdir" ]; do
[ ! -L "$testdir" ] || fail 4 "homedir component is a symlink: $testdir"
[ -d "$testdir" ] || fail 5 "homedir component is not a directory: $testdir"
[ $n_max -gt 0 ] || fail 6 "too many homedir components: $homedir"
n_max=$((n_max - 1))
# go to next-higher path component:
testdir="$(dirname -- "$testdir")" || fail 7 "could not resolve component: $homedir"
done
true
}
## Integrity checks: ##########################################################
homedir="$(user_homedir "$username")"
# Now we know that the user exists and has an existing homedir.
# Make sure that the homedir does not contain any dangerous characters.
# In theory, they should not be a real problem,
# but we'd have to escape them in our final output.
# Since special characters in homedir names are really uncommon,
# we'll just reject them altogether:
homedir_name_check "$homedir"
# None of the homedir components can be a symlink!
# Otherwise the main account could remove the sub account's homedir
# and replace it with a symlink to, say, /root/secrets/.
# No matter what modes /root/ has -- as long as the sub account
# could enter secrets/ itself, they can read all files there.
# To prevent this, we check all path components:
homedir_symlink_check "$homedir"
## Prepare the environment: ###################################################
# The /jail directory has to belong to root (or internal-sftp won't chroot).
# Restrictive modes are essential, or local users might access the jail mountpoints,
# circumventing the /home/$BASE_USER modes.
chmod -- 0700 "$MOUNT_TO"
chown -- root:root "$MOUNT_TO"
## Perform the mount: #########################################################
# Finally tell autofs to bind-mount /jail/$username/$homedir to the real $homedir.
#
# Because /jail/$username does not actually exist (yet),
# autofs will temporarily create it (root:root 0755)
# and all the other path components of $homedir, including home/.
#
# But if we were to emit a "/" mount too (expanding to /jail/$username),
# it would have to contain an empty $homedir mount point!
# "key" is the relative mountpoint directory. Our base directory is /jail,
# so the key is the requested directory name therein -- the username.
# "location" is the device/directory/network resource to mount.
# The ":" prefix indicates a local device/directory.
# key [options] location
printf -- '"/%s" -fstype=bind ":%s"\n' "$homedir" "$homedir"
这就是它的基本原理。另外,我一直听说 S3FS 不可靠。我不确定这是否属实,但如果你最终遇到问题,我会使用 AWS 文件网关来覆盖 S3。