Сам себе хостинг или о недооценённых утилитах

Зайчатки разума

Записная книжка айтишника

Сам себе хостинг или о недооценённых утилитах

2018-10-21 00:49:04 — Evgeniy Shumilov
- Та-ак, а вы что, ещё и права на файлы за меня выдавать будете?
- Ага-а-а!

  О чём вы думаете, когда слышите аббревиатуру FTP? На меня накатывает ностальгия. Сразу вспоминаются всякие уютные ламповые локалочки, сетевые карты rtl8139, серенькие восьмипортовые коммутаторы D-Link в пластиковых корпусах, папки Public и Private, полные варезом - фильмы, музыка, софт, игры... Одним словом, машинка времени в моей голове телепортирует меня куда-то лет на 15 назад. Много воды утекло с тех пор, но когда кто-то просит совета, как построить себе миниатюрный хостинг, часто в числе прочего я в том или ином виде получаю вопрос: "как поднять FTP"? И каждый раз меня это удивляет. А чем sftp не угодил? Да, это медленнее, но во-первых, скорость интернет соединения сейчас уже не та, что была 15 лет тому, во-вторых, у sftp на борту шифрование и в-третьих, sftp практически всегда уже есть там, где присутствует openssh.

  Да, sftp, реализованный средствами OpenSSH не лишён ряда недостатков. Хотелось найти некое решение, которое бы позволило быстро и просто реализовать доступ к машине для разных пользователей, для каждого в свою папку, желательно с chroot. Всё заканчивалось длинными ролями на ансибле, которые переписывали конфиги openssh, добавляли пользователей в нужные гргуппы, ставили пакет управления расширенными правами доступа, дёргали всякие setfacl и прочее. И тут меня попросил мой хороший друг (Иван, спасибо тебе!) помочь ему настроить сервер, а пользователям в качестве шелла поставить MySecureShell. Ранее я с таким зверем не сталкивался, да и ни от кого из моих знакомых за те почти 15 лет, что я общаюсь с linux, не слышал. Судя по всему, это не особо популярное решение. Я бы сказал, незаслуженно обойдённое вниманием. Да вы только посмотрите на их логотип! Похоже, это пингвин, прямо поверх которого начали разводить печатную плату (что похоже, сказалось на его настроении), при этом он ещё кому-то угражает двумя логотипами OpenBSD! Интересно, на каких веществах сидел автор? Ладно, этот вопрос оставим наркологам. Лучше посмотрим в конфигурационный файл. Почитав, что там написано, я понял, что эта штука умеет всё, о чём я мечтал и многое из того, о чём я мечтать не смел.


Возможности

  Итак, что же умеет эта штука:

  • при установке в качестве shell, открывает доступ пользователю только по sftp с chroot
  • ограничивать upload/download глобально
  • ограничивать upload/download для любого пользователя или для группы
  • устанваливать таймаут пользователю/группе
  • скрывать файлы и директории, к которым нет доступа
  • заменять в отображении владельца/группу
  • отображать симлинки как то, на что они указывают (это трудно переоценить)
  • принудительно указывать кодировку
  • вести отдельные логи для пользователя/группы
  • указать для любого пользователя или группы дату, до которой активен аккаунт
  • ограничить количество коннектов глобально, с одного IP, для одного юзера
  • указать дефолтные права на создаваемые файлы/папки (необходимость в acl отпадает)
  • указать путь к директории, используя переменные окружения
  • так же можно в конфиге дать пользователю права на shell
  • можно инклудить конфигурационные файлы
  • можно задавать параметры и настройки так же для диапазонов адресов

  Ещё в конфигурационном файле есть описание неких виртуалхостов. Я подозреваю, что оно ещё каким-то образом определяет, по какому домену вы пришли и в зависимости от этого сможет например, заворачивать ваш коннект в конкретную папку. Не уверен, не проверял - это лишь моё предположение, мне пока и без этого функционала есть, с чем поэкспериментировать. Если честно, то всё это очень впечатляет! Это просто идеальная утилита для решения задачи доступа к файлам в рамках небольшого карманного веб хостинга или если вы хотите использовать какую-нибудь домашнюю raspberry/banana/orange/добавьте-по-вкусу pi в том числе и в качестве файл сервера для синхронизации например, фотографий или документов. А при использовании sshfs такую шару можно вообще подмонтировать куда угодно, в том числе открыть на смартфоне через какой-нибудь X-Plore.

Переходим к практике

sudo apt-get install mysecureshell
sudo vim /etc/ssh/sftp_config

  Файл щедро обмазан комментариями и примерами, но если вам этого недостаточно, то вам сюда: https://mysecureshell.readthedocs.io/en/latest/index.html. Я ниже просто приведу пример того, что осталось в моём конфиге после редактирования и уборки лишних комментариев, это версия с минимальным функционалом, чтобы просто показать, что это работает:

<Default>
        GlobalDownload          0
        GlobalUpload            0
        Download                500k
        Upload                  0
        StayAtHome              true
        VirtualChroot           true
        LimitConnection         100
        LimitConnectionByUser   10
        LimitConnectionByIP     10
        Home                    /mnt/hd1000/private/$USER/
        IdleTimeOut             5m
        ResolveIP               true
        HideNoAccess            true
        ShowLinksAsLinks        true
	# Так как у нас не демон, а шелл, запускаемый с правами пользователя, 
	# то логи мы сможем класть только в ту директорию, куда у него есть доступ
        LogFile                 /tmp/sftp-server_ftp.log
        Charset                 "UTF8"
</Default>

  А теперь немного изменим и дополним скрипт из предыдущего поста. Добавим к нему создание директории, которая будет доступна по sftp, выдадим на неё нужные права, уберём генерацию пользовательской директории с дефолтными конфигами и заменим шелл на mysecureshell. Так же реализуем удаление пользователя и предложим выбор - нужно ли удалять директорию с его файлами. В качестве бонуса положим рядом скрипт для логирования из этого поста:

#!/bin/sh
# MySecureShell users generation script by Evgeniy Shumilov <evgeniy.shumilov@gmail.com>

# Расположение пользовательских директорий и дефолтная группа
STORAGE=/mnt/hd1000/private
GROUP=media

# Переходим в директорию, из которой вызван скрипт и загружаем логгер
cd `dirname "$0"`
[ -f "eslogger" ] || wget https://raw.githubusercontent.com/alive-corpse/eslogger/master/eslogger
. ./eslogger

# Проверяем, что скрипт запущен с полными правами, иначе запускаем через sudo 
# c тем же набором параметров ($*), а затем выходим c тем же кодом ($?),
# чтобы не пытаться выполнять содержимое второй раз
if [ `whoami` != "root" ]; then
    l "This script should be started with root privileges, restarting with sudo..."
    sudo $0 $*
    exit $?
fi

# Проверяем наличие mysecureshell и mkpasswd
[ -z "$(which mysecureshell)" ] && l fe "Can't found mysecureshell binary" 4
[ -z "$(which mkpasswd)" ] && l fe "Can't found mkpasswd binary (usually part of whois package)" 5

# Добавляем help
help() {
    d
        echo "Appeding new user/users:"
        echo "  Usage: $0 add <username1> [username2] ... [usernameN]"
        echo "  Example: $0 add user1 user2 user3"
        echo
        echo "Removing user/users:"
        echo "  Usage: $0 del <username1> [username2] ... [usernameN]"
        echo "  Example: $0 del user1 user2 user3"
    d
    [ -n "$1" ] && exit "$1"
    exit 1
}

pwg() {
        [ -z "$1" ] && len="8" || len="$1"
        tr -dc A-Za-z0-9 < /dev/urandom | head -c "$len" | xargs
}

# Небольшой чит - функция для предоставления пользователю выбора
# Возвращает один из вариантов "y" или "n"
choise() {                                                                                                                                                                   
    ch=''                                                                                                                                                                    
    while [ -z "$(echo "$ch" | sed '/^[yn]$/!d')" ]; do                                                                                                                      
        echo -n "$1 " > /dev/stderr                                                                                                      
        read ch                                                                                                                                                              
    done                                                                                                                                                                     
    echo "$ch"                                                                                                                                                               
} 


addusers() {
    for u in $1; do
        # Заменяем все символы, которые не являются буквами и цифрами на символ "-"
        uname="$(echo "$u" | sed 's/[^a-zA-Z0-9]/-/g')"
    l "Trying to add user $uname..."
        # Берём пароль длиной в 16 символов
        passwd=`pwg 16`
        # Генерируем ещё одну случайную последовательность для соли
        salt=`pwg 8`
        # Получаем хэш пароля
        phash=`echo "$passwd" | mkpasswd -s -m sha-512 -S "$salt"`
        # Пытаемся создать пользователя и выводим информацию об имени и пароле
        if useradd -s /usr/bin/mysecureshell -d "$STORAGE/$uname" -M -p "$phash" -g "$GROUP" "$uname"; then
            l "User $uname is added, password: $passwd"
            # Создаём пользовательскую директорию и выдаём ему права:
            mkdir -p "$STORAGE/$uname"
            chown "$uname:$GROUP" "$STORAGE/$uname"
        else
            l e "Fail to add user $uname"
        fi
    done
}

delusers() {
    for u in $1; do
        uname="$(echo "$u" | sed 's/[^a-zA-Z0-9]/-/g')"
    l "Trying to remove user $uname..."
        # Для начала проверим, что пользователь существует и заодно, что его id > 1000
    # чтобы не удалить какого-нибудь системного пользователя
        uid=`id "$uname" 2>&1`
    if [ -n "$(echo "$uid" | grep -F 'no such user')" ]; then
            l w "User $uname is not exists"
        else
            if [ "$(echo "$uid" | sed 's/^.*uid=//;s/(.*//')" -lt 1000 ]; then
                l w "User $uname has id lower than 1000"
            else
                if deluser "$uname"; then
                    l "User is deleted"
                    # Проверяем, что есть директория и предлагаем её удалить,
                    # будьте с этим осторожны!
                    if [ -d "$STORAGE/$uname" ]; then
            if [ -n "$(choise "Do you want to remove user data (y/n)?") | grep -F 'y'" ]; then
                            l "Removing directory $STORAGE/$uname..."
                            rm -rfv "$STORAGE/$uname"
                        fi
                    fi
                else
                    l e "Fail to delete user $uname"
                fi
            fi
        fi
    done
}


[ -z "$1" ] && l w "Empty command" && help 1
[ -z "$2" ] && l w "Empty user name" && help 2

users=`echo "$*" | cut -d " " -f 2-`

case $1 in
    add)
        addusers "$users"
    ;;
    del)
        delusers "$users"
    ;;
    *)
        l w "Wrong command $1"
        help 3
    ;;
esac

Примеры запуска и вывода

$ ./mss.sh 
2018.10.20-23:38:28    INFO: This script should be started with root privileges, restarting with sudo...
2018.10.20-23:38:28 WARNING: Empty command
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
Appeding new user/users:
  Usage: ./mss.sh add <username1> [username2] ... [usernameN]
  Example: ./mss.sh add user1 user2 user3

Removing user/users:
  Usage: ./mss.sh del <username1> [username2] ... [usernameN]
  Example: ./mss.sh del user1 user2 user3
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

$ ./mss.sh add user1 user2 user3
2018.10.20-23:38:38    INFO: This script should be started with root privileges, restarting with sudo...
2018.10.20-23:38:38    INFO: Trying to add user user1...
2018.10.20-23:38:38    INFO: User user1 is added, password: pU4YerjLw5oeWvFZ
2018.10.20-23:38:38    INFO: Trying to add user user2...
2018.10.20-23:38:38    INFO: User user2 is added, password: iPfZa4ARG5L3IMC1
2018.10.20-23:38:38    INFO: Trying to add user user3...
2018.10.20-23:38:38    INFO: User user3 is added, password: nVogh4myQO1RVjoE

$ ls -lah /mnt/hd1000/private/ | grep user1
drwxr-xr-x  2 user1     media     4.0K Oct 20 23:38 user1

$ ./mss.sh del user2 user3 user4
2018.10.20-23:45:04    INFO: This script should be started with root privileges, restarting with sudo...
2018.10.20-23:45:04    INFO: Trying to remove user user2...
Removing user `user2' ...
Done.
2018.10.20-23:45:05    INFO: User is deleted
Do you want to remove user data (y/n)? y
2018.10.20-23:45:07    INFO: Removing directory /mnt/hd1000/private/user2...
removed directory: ‘/mnt/hd1000/private/user2’
2018.10.20-23:45:07    INFO: Trying to remove user user3...
Removing user `user3' ...
Done.
2018.10.20-23:45:07    INFO: User is deleted
Do you want to remove user data (y/n)? y
2018.10.20-23:45:11    INFO: Removing directory /mnt/hd1000/private/user3...
removed directory: ‘/mnt/hd1000/private/user3’
2018.10.20-23:45:11    INFO: Trying to remove user user4...
2018.10.20-23:45:11 WARNING: User user4 is not exists

  Теперь попробуем с полученным паролем зайти по ssh и по sftp, посмотреть список доступных команд и что-нибудь загрузить.

$ ssh user1@localhost 
user1@localhost's password: 

The programs included with the Debian GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Debian GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
Shell access is disabled !Connection to localhost closed.

  Как видим, по ssh нас не пускает, это хорошо, пробуем sftp

$ sftp user1@localhost
user1@localhost's password: 
Connected to localhost.
sftp> pwd
Remote working directory: /
sftp> ?
Available commands:
bye                                Quit sftp
cd path                            Change remote directory to 'path'
chgrp grp path                     Change group of file 'path' to 'grp'
chmod mode path                    Change permissions of file 'path' to 'mode'
chown own path                     Change owner of file 'path' to 'own'
df [-hi] [path]                    Display statistics for current directory or
                                   filesystem containing 'path'
exit                               Quit sftp
get [-Ppr] remote [local]          Download file
reget remote [local]               Resume download file
reput [local] remote               Resume upload file
help                               Display this help text
lcd path                           Change local directory to 'path'
lls [ls-options [path]]            Display local directory listing
lmkdir path                        Create local directory
ln [-s] oldpath newpath            Link remote file (-s for symlink)
lpwd                               Print local working directory
ls [-1afhlnrSt] [path]             Display remote directory listing
lumask umask                       Set local umask to 'umask'
mkdir path                         Create remote directory
progress                           Toggle display of progress meter
put [-Ppr] local [remote]          Upload file
pwd                                Display remote working directory
quit                               Quit sftp
rename oldpath newpath             Rename remote file
rm path                            Delete remote file
rmdir path                         Remove remote directory
symlink oldpath newpath            Symlink remote file
version                            Show SFTP version
!command                           Execute 'command' in local shell
!                                  Escape to local shell
?                                  Synonym for help

  Попробуем создать директорию и залить туда в качестве теста тот же самый скрипт, который выполняем.

sftp> mkdir test
sftp> cd test
sftp> lls
eslogger  mss.sh
sftp> put *
Uploading eslogger to /test/eslogger
eslogger                                            100% 3777     3.7KB/s   00:00    
Uploading mss.sh to /test/mss.sh
mss.sh                                              100% 6077     5.9KB/s   00:00    
sftp> ls
eslogger  mss.sh

  Проверим:

$ ls -lah /mnt/hd1000/private/user1/
total 12K
drwxr-xr-x 3 user1 media 4.0K Oct 20 23:58 .
drwxr-xr-x 6 root  root  4.0K Oct 20 23:45 ..
drwxrwxrwx 2 user1 media 4.0K Oct 20 23:58 test

  Как видите, всё работает. Возможно в одном из следующих постов добавлю в этот скрипт создание структуры директорий и конфигов для nginx как обычно, с шахматами и поэтессами.

  Так же скрипт можно взять тут:

wget https://shumiloff.ru/stuff/mss.sh; chmod +x mss.sh

Теги: ssh, shell, sftp, админское

comments powered by Disqus