Сам себе хостинг или о недооценённых утилитах
- Ага-а-а!
О чём вы думаете, когда слышите аббревиатуру 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