使用 OpenSSH 配置 SFTP 并通过 Amazon EC2 上的 S3FS 安装 AWS S3 Bucket

使用 OpenSSH 配置 SFTP 并通过 Amazon EC2 上的 S3FS 安装 AWS S3 Bucket

如何允许多个 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 rootdrwxrwxr-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。

相关内容