Криптовелосипед, или USB ключи от сервера

в 15:13, , рубрики: linux, Песочница, системное администрирование, метки:

Многие системные администраторы не раз «изобретали велосипед», для выполнения некоторых, казалось бы, вполне тривиальных задач. Подробную историю, навеянную мне логами приехавшего на профилактику сервера и восстановленную по служебной документации, об изобретении одного из таких велосипедов, я и хочу поведать. Осторожно много букв и(з) консоли!


Примерно 2 года назад мне для работы был предоставлен сервер в стоечном исполнении, содержащий операционную систему GNU/linux (читай достаточно древнюю ubuntu), и 2 USB флеш-накопителя производства фирмы Transcend обьёмом 16 гигабайт (уж не знаю, зачем такой объём) для изготовления ключевых носителей.
Была поставлена задача: зашифровать разделы с базами данных находящиеся на сервере для предотвращения несанкционированного доступа.
Для реализации задачи была выбрана технология шифрования разделов LUKS.
Также понадобились следующие инструменты:

  • Внешний носитель (usb hdd), содержащий загружаемую операционную систему GNU/linux и достаточное количество свободного пространства;
  • Компьютер с операционной системой arch-linux для написания и тестирования скриптов;

Первоначально планировалось воспользоваться встроенным в дистрибутив алгоритмом монтирования и подключения шифрованных файловых систем, однако то ли недоcтаток опыта, то ли какие-то другие причины привели меня в тупик. Ну да обо всём по порядку.

В начале, конечно же, я загрузился с внешнего носителя и сделал полный бекап всех данных с сервера, заодно мной был изучен файл /etc/fstab. Так как пароля суперпользователя никто не знал сразу после снятия бэкапа я модифицировал файл /etc/shadow, вписав туда хеш известного мне пароля в строку пользователя root. Получив, таким образом, доступ в установленную систему я взялся за дело:
Подключился к сети:

# dhclient

Установил утилиту cryptsetup, включающую в себя инструменты для работы системы шифрования LUKS:

# apt-get update
# apt-get install cryptsetup

Сгенерировал псевдослучайную последовательность из 256 символов, записанную в файл "/tmp/key":

# dd if=/dev/random of=/tmp/key bs=1 count=256

К слову tmp монтировался в оперативу. Установил тип второго раздела первого тома в crypto_LUKS и создал виртуальный раздел "/dev/mapper/dtb", отображаемый в реальный зашифрованный при помощи алгоритмов LUKS:

# cryptsetup luksFormat /dev/sdb2 /tmp/key
# cryptsetup luksOpen /dev/sdb2 dtb

На виртуальном разделе, отображаемом в зашифрованный, создал новую файловую систему ext4, и смонтировал в директорию /mnt:

# mkfs.ext4 /dev/mapper/dtb
# mount /dev/mapper/dtb /mnt

В целях выполнения переноса данных, при помощи команды:

# /etc/init.d/postgresql stop

остановил демон portagesql, и помощи программы Midnight Commander скопировал все файлы из /var/lib/pgsql в /mnt, после чего каталог /var/lib/pgsql очистил. Шифрованный второй раздел первого тома перемонтировал в директорию/var/lib/pgsql:

# umount /dev/mapper/dtb
# mount /dev/mapper/dtb /var/lib/pgsql

Файлик "/tmp/key", используемый в качестве ключа шифрования раздела, отправил на одну из флешек.

Как приготовил флешку

Флешки привезли прямо в упаковке. Когда я воткнул одну из флешек к себе в комп, посмотреть отформатирована ли она и в какой файловой системе, оказалось что перед основным разделом есть семь мегабайт свободного места. Сразу пришло в голову как защитить юзера от случайного удаления ключика! Создал впереди раздел, форматнул в ext2. Под windows раздел не видно вовсе, даже если юзер форматнёт основной раздел флешка не утратить свои серверозапускательные возможности.

При помощи программы Midnight Commander и утилит коммандной оболочки в файл /etc/cryptsetup была внесена конфигурация зашифрованного раздела. Однако, в ходе тестовой перезагрузки было установлено что из за особенностей загрузки операционной системы, она не может смонтировать ключевой носитель из /etc/fstab до того момента как происходит обработка файла /etc/cryptsetup, в результате зашифрованный раздел оказывается не примонтированным, так как он не может быть смонтирован без файла ключа.

Необходимые инструкции (монтирование ключевого носителя, активация шифрованного раздела, размонтирование ключевого раздела и монтирование зашифрованного раздела) были внесены при помощи соответствующих команд в файл /etc/rc.local

Это действие дало положительные результаты (после окончания загрузки раздел был примонтирован в нужное место) однако файл rc.local запускался намного позже демона postgresql что могло привести к непредсказуемым последствиям так как файлы базы данных монтировались прямо во время работы демона.

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

В результате был создан специальный стартовый bash-скрипт который выполнял всё вышеуказанные функции, а именно

  1. Производил поиск и монтирование ключевого устройства
  2. В случае обнаружения и успешного монтирования ключевого устройства подключал и монтировал зашифрованные разделы
  3. Размонтировал ключевое устройство для безопасного извлечения
  4. Независимо от результата предыдущих операций обеспечивал бы монтирование раздела подкачки на зашифрованный раздел

Также в процессе написания и отладки скрипта удалось реализовать следующие возможности:

  • Возмоность централизованного управления всеми зашифрованными по алгоритму luks томами при помощи специального файла с простым синтаксисом схожим с синтаксисом файла fstab
  • Теоретическая возможность использовать неограниченное количество ключей для неограниченного количества зашифрованных разделов (на самом сушествуют определённые ограничения связанные с исчерпанием системных ресурсов)
  • Возможность создавать различные независимые ключевые носители с различным набором ключевых файлов
  • Возможность монтировать и размонтировать различные наборы зашифрованных дисков

В процессе написания скрипта было установлено, что в репозиториях ubuntu содержится устаревшая версия пакета cryptsetup, (не умела генерить раздел с заданным UUID) поэтому актуальная версия программы была собрана из исходных кодов и установлена вместо стандартного пакета ubuntu.
Bash-скрипт был установлен в /etc/init.d/encryptdb. После установки скрипта производились различные отладочные действия, не изменяющие общего состояния системы. После отладки скрипт, при помощи команды update-rc.d encryptdb defaults, был активирован для запуска в автоматическом режиме. Вместе со скриптом были установлены файлы настроек /etc/keycrypt/keycryptab и /etc/keycrypt/keydrv.

Приложения:

  1. Скрипт encryptdb с комментариями на русском языке

    
    #!/bin/bash
    ### BEGIN INIT INFO
    # Provides:          cryptdisks
    # Required-Start:    checkroot cryptdisks-early
    # Required-Stop:     umountroot cryptdisks-early
    # Should-Start:      udev mdadm-raid lvm2
    # Should-Stop:       udev mdadm-raid lvm2
    # X-Start-Before:    checkfs
    # X-Interactive:     true
    # Default-Start:     2 3 4 5
    # Default-Stop:      0 6
    # Short-Description: Setup remaining encrypted block devices.
    # Description:
    ### END INIT INFO
    
    # Этот скрипт находит сьёмные носители, содержащие файл ключа,
    # подкключает и монтирует зашифрованные файловые системы и раздел подкачки
    
    KEYCRYPTAB_DIR=/etc/keycrypt
    # Рабочая директория
    
    KEYCRYPTAB_FILE=$KEYCRYPTAB_DIR/keycryptab
    # Этот файл содержит параметры монтирования всех разделов
    # которые необходимо монтировать при помощи ключей
    # В этом файле обязательно должен быть финальный перевод строки или комментарий в конце
    # Синтаксис:
    # <uuid раздела> <имя файла ключа> <имя радела для links> <точка монтирования>
    # Для раздела подкачки в качестве точки монтирования нужно указать "swap".
    # Имя файла ключа для раздела подкачки может содержать всего 2 значиния: "none" и "random"
    # Use the option "none" is not recommended.
    # Examples:
    # 11111111-2222-3333-4444-555555555555 random swap1 swap
    # #safety swap partition. It no need any keyfile, but must be encrypted
    # 12345678-1234-4321-1234-567890123456 harry.key harry /home/harry
    # #home directory for Harry, Harry have the "harry.key" in his flash drive
    # 66666666-9999-8888-7777-000000000000 ntldr public "/var/ftp"
    # #publuc directory, all staff have the "ntldr" file in flash drives
    
    KEYDRIVER_FILE=$KEYCRYPTAB_DIR/keydrv
    # Этот файл содержит параметры ключевых носителей
    # В этом файле обязательно должен быть финальный перевод строки или комментарий в конце
    # Синтаксис:
    # <uuid> <timeout> <dotmount>
    # Examples:
    # kkkkkkkk-kkkk-kkkk-kkkk-kkkkkkkkkkkk 20 /media/keys
    
    
    SWAPCLEAN_FILE=$KEYCRYPTAB_DIR/swapclean.flg
    # NOTE!!! If u are not using the encription swap
    # u need to run "dd if=/dev/urandom of=/u/swap/partition".
    # Otherwise encryption will not make sense.
    # I was include this functional on the skript, but
    # it will take a very long time to load OS.
    
    #=====================Begin of script=====================
    # Do not edit next if you are not sure what you are doing!
    
    #DBG="on"
    # Разкоментировать строку выше для вывода отладки
    
    UMOUNT_FLAG=""
    # менять на свой страх и риск
    
    
    # Функция вывода отладки, печатает переданное сообщения желтым цветом, 6 параметров (НЕ СЛОВ! слов может быть хоть миллион)
    DEBUG()
    {
        if [ "$DBG" = "on" ]
        then
            echo -e "E[33;40m$1 $2 $3 $4 $5 $6"; tput sgr0
        fi
    }
    
    # Функция парсит строчку с параметрами разделов и выставляет значения переменных
    SetStruct()
    {
    str=`echo $1 | sed 's/#.*/ /g'`
    n=0
        for arg in $str
        do
            let "n+=1"
            case "$n" in
            1)
                CRYPT_UUID=$arg
                ;;
            2)
                KEY_FILENAME=$arg
                ;;
            3)
                CRYPT_NAME=$arg
                ;;
            4)
                CRYPT_MOUNTPOINT=$arg
                return 0
                ;;
            *)
                DEBUG "too many arguments: $arg"
                ;;
            esac
        done
        return 1
    }
    
    # Функция парсит строчку с параметрами ключевого носителя и выставляет значения переменных
    SetKey()
    {
    nk=0
    str=`echo $1 | sed 's/#.*/ /g'`
        for arg in $str
        do
            let "nk+=1"
            case "$nk" in
            1)
                KEY_UUID=$arg
                ;;
            2)
                KEY_TIMEOUT=$arg
                if (( KEY_TIMEOUT < 0 ))
                then
                    echo "Invalid key timeout for $KEY_UUID"
                    KEY_TIMEOUT=1
                fi
                if (( KEY_TIMEOUT > 60 ))
                then
                    echo "Invalid key timeout for $KEY_UUID"
                    KEY_TIMEOUT=60
                fi
                DEBUG "KEY_TIMEOUT=$KEY_TIMEOUT"
                ;;
            3)
                KEY_MOUNTPOINT=$arg
                return 0
                ;;
            *)
                DEBUG "too many arguments: $arg"
                ;;
            esac
        done
        return 1
    }
    
    # функция монтирует ключевой носитель
    PrepareKeyDrv()
    {
        if [ ! -e "/dev/disk/by-uuid/$KEY_UUID" ]
        then
        #если ключевой носитель не найден
            echo -en "Waiting $KEY_TIMEOUT seconds for the key device  r"
            #ожидаем пока загрузится модуль
            for (( n = ++KEY_TIMEOUT; n ; n-- ))
            do
                if [ ! -e "/dev/disk/by-uuid/$KEY_UUID" ]
                then
                    (( KEY_TIMEOUT-- ))
                    echo -en "Waiting $KEY_TIMEOUT seconds for the key device  r"
                else
                    echo
                    echo "Key device was found!"
                    DEBUG $KEY_UUID
                    break
                fi
                if [ "$n" = "1" ]
                then
                    echo
                    echo "Not found a key device!"
                    DEBUG "Now completing PrepareKeyDrv()"
                    #не нашли, выходим
                    return 1
                fi
                sleep 1 # it is not a debug! Do not comment it!
            done
        fi
        #нашли, монтируем, определяя фс по длинне uuid
        DEBUG "Mounting the key device"
        if [ ! -d "$KEY_MOUNTPOINT" ]; then
            RM_KMP=1
            mkdir $KEY_MOUNTPOINT
        fi
        uuid_l=`echo $KEY_UUID | wc -m`
        case "$uuid_l" in
            10)
                mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -t vfat -o ro
                DEBUG "fat detected"
                ;;
            17)
                mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -t ntfs-3g -o force,ro
                DEBUG "ntfs detected"
                ;;
            37)
                mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -o ro
                DEBUG `mount | grep $KEY_MOUNTPOINT `
                ;;
            *)
                DEBUG "Can not identify type of the file system on the specified uuid:"
                DEBUG "$KEY_UUID"
                mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -o ro
                ;;
        esac
    }
    
    # Функция размонтирует ключевые носители для извлечения
    UmountKey()
    {
        DEBUG "Unmounting the key device"
        umount $1 /dev/disk/by-uuid/$KEY_UUID
        if [ "$RM_KMP" = "1" ]; then
            rmdir $KEY_MOUNTPOINT
        fi
    }
    
    # Функция подготавливает разделы для подкачки
    PrepareSwap()
    {
        if [ "$KEY_FILENAME" = "random" ]
        then
        # Если используем шифрованные разделы подкачки
            if [ ! -e "/dev/disk/by-uuid/$CRYPT_UUID" ]
            then
            # Если раздела нет
                # ругаемся
                echo "Not found any partition for swap with UUID:"
                echo "$CRYPT_UUID"
                ### Если тебе в голову придёт изменить логику проверок ДОПИШИ СЮДА ВЫХОД ИЗ ФУНКЦИИ
            else
            # Раздел существует
                echo "Prepare to encripting swap"
                DEBUG "Disable all swaps"
                # Отрубаем всю подкачку (кстати, откуда она у нас ?)
                swapoff -a
                DEBUG "Regenerating new temporary key"
                # Создаём папку для временного ключа
                mkdir /tmp/key ### НЕ ВЗДУМАЙ МЕНЯТЬ ПУТЬ особенно если ты не позаботился о том чтобы ключ не записался на винт
                # Монтируем в эту папку кусочек оперативы (свап же мы отключили? Ключик на винте не окажется?)
                mount -t ramfs none /tmp/key -o maxsize=1
                # генерим ключик
                dd if=/dev/urandom of=/tmp/key/swapkey$CRYPT_UUID bs=1 count=256 &> /dev/null
                DEBUG "Configuring encrypt on the swap partition"
                # создаём на партиции шифрованный раздел
                echo "YES"|cryptsetup luksFormat /dev/disk/by-uuid/$CRYPT_UUID /tmp/key/swapkey$CRYPT_UUID --uuid=$CRYPT_UUID
                # подключаем его
                DEBUG "Openinig swap partition for operations"
                cryptsetup luksOpen /dev/disk/by-uuid/$CRYPT_UUID $CRYPT_NAME --key-file /tmp/key/swapkey$CRYPT_UUID
                # ключик уничтожаем
                DEBUG "Erasing temporary key"
                rm -r -f /tmp/key/swapkey$CRYPT_UUID
                # Размонтируем рамдиск
                umount none
                # генерируеим новый UUID для свопа (не путать с uuid luks который имеет физический раздел)
                DEBUG "Regenerating swapfs uuid"
                FS_CRYPT_UUID=`uuidgen`
                #echo $FS_CRYPT_UUID > $KEYCRYPTAB_DIR/swaps/$CRYPT_UUID
                DEBUG "Creating swap format"
                if [ -e "$SWAPCLEAN_FILE" ]
                then
                # Если в прошлый раз кто-то замонтировал раздел не шифруя забиваем его мусором чтоб стереть остатки инфы
                    ### неплохо бы однако проверять это не по наличию а по отсутсвию файла, это более секьюрно, и при установке скрипт сам позаботится о том чтобы всё сделать правильно.
                    echo "Swap partition was mounted unsafe, cleaning..."
                    echo -e 'It may take a long time. e[31;40mDo not halt the computer! e[0m'
                    S=`fdisk -s /dev/mapper/$CRYPT_NAME`
                    let "S *= 1024"
                    dd if=/dev/urandom | pv -s $S | dd of=/dev/mapper/$CRYPT_NAME 2> /dev/null
                    rm -r -f $SWAPCLEAN_FILE
                fi
                # создаём swapfs
                mkswap -f -U $FS_CRYPT_UUID /dev/mapper/$CRYPT_NAME &> /dev/null
                # врубаем подкачку
                DEBUG "Activating swap"
                swapon -U $FS_CRYPT_UUID
            fi
        else
        # Нешифрованный свап, обычное монтирование раздела со свапом токлько много мата и специальный файлик-флаг
            if [ ! -e "/dev/disk/by-uuid/$CRYPT_UUID" ]
            then
                echo "Not found any partition for swap with UUID:"
                echo "$CRYPT_UUID"
            else
                chkswapt=`cat /proc/swaps | grep "$CRYPT_NAME"`
                if [ ! "$chkswapt" = "" ]
                then
                    echo "Oops! e[31;40mYou already have the swap! e[0m"
                    cat /proc/swaps
                    echo "Сheck your 'fstab', 'cryptab' and '$KEYCRYPTAB_FILE'"
                    echo "files for duplicate entries for swap partition"
                    echo "In future use only one of these files to manage swaps"
                    return 0
                else
                    echo -e 'e[31;40mWARNING!!! e[0mThe system uses the unface way to manage swap!'
                    echo 'You need to use value "random" of <key filename> on the'
                    echo "$KEYCRYPTAB_FILE file for all swap partition!"
                fi
                swapoff -a
                mkswap -f -U $CRYPT_UUID /dev/disk/by-uuid/$CRYPT_UUID &> /dev/null
                swapon -U $CRYPT_UUID && echo "Remoove this file for disable cleaning swap on boot time" > $SWAPCLEAN_FILE
    #           echo $CRYPT_UUID > $KEYCRYPTAB_DIR/swaps/$CRYPT_UUID
            fi
        fi
    }
    
    # Функция подключает и монтирует шифрованные разделы
    PrepareVolumes()
    {
        cat $KEYCRYPTAB_FILE | while read line; do
        #Читаем по строке из файла с параметрами монтирования
            if SetStruct "$line"
            then
            # Если строчка успешно распарсилась
                if [ ! -e "/dev/disk/by-uuid/$CRYPT_UUID" ]
                then
                # Если такого раздела нет
                    # Ругаемся, переходим к следующему
                    echo "Not found encrypted partition with UUID:"
                    echo "$CRYPT_UUID"
                else
                # Если раздел существует
                    if [[ "$CRYPT_MOUNTPOINT" = "swap" && "$1" = "swaps" ]]
                    then
                    # Если раздел для подкачки и процедура запущена с параметром "swaps"
                        # Запускаем для монтирования свап раздела специальную процедуру
                        PrepareSwap
                    else
                    # Если это обычный раздел
                        if [[ "$CRYPT_MOUNTPOINT" != "swap" && "$1" != "swaps" ]] ### Уберёшь первое условие, сломаю руку! Ибо сюда может попасть свап раздел и в лучшем случае получишь срач на вывод ошибок от моунта, в худшекм останешься без свопа или что ещё хуже с нешифрованным свопом.
                        then
                        # И если процедура не запущена с параметром "swaps"
                            # Подключаем шифрованный раздел
                            echo "Opening encrypted volume"
                            cryptsetup luksOpen /dev/disk/by-uuid/$CRYPT_UUID $CRYPT_NAME --key-file $KEY_MOUNTPOINT/$KEY_FILENAME
                            if [ -e "/dev/mapper/$CRYPT_NAME" ]
                            then
                            # Если подключился
                                # Создаём точку монтирования и монтируем
                                echo "Mounting encrypted volume"
                                if [ ! -d "$CRYPT_MOUNTPOINT" ]; then
                                    mkdir $CRYPT_MOUNTPOINT
                                fi
                                mount /dev/mapper/$CRYPT_NAME $CRYPT_MOUNTPOINT
                            fi
                        fi
                    fi
                fi
            fi
        done
    }
    
    
     case "$1" in
    start)
    # Эта часть выполняется при запуске скрипта с параметром start
    # Например при запуске компьютера
    
    #     if [ -d "$KEYCRYPTAB_DIR/swaps/" ]
    #     then
    #         rm -r -f $KEYCRYPTAB_DIR/swaps/*.*
    #     else
    #         mkdir --parents $KEYCRYPTAB_DIR/swaps/
    #     fi
    
        cat $KEYDRIVER_FILE | while read line; do
        #Читаем по строке из файла ключей
            if SetKey "$line"
            then
            # Если строчка успешно распарсилась
                if PrepareKeyDrv
                then
                # Если ключевой носитель удалось смонтировать
                    # Монтируем шифрованные разделы
                    PrepareVolumes
                    # Размонтируем ключевой носитель
                    UmountKey
                fi
            fi
        done
    
        #Монтируем разделы подкачки
        PrepareVolumes swaps
        DEBUG "Now comleting"
        ;;
     stop)
    # Эта часть выполняется при запуске скрипта с параметром stор
    # Например при отгрузке OS
        cat $KEYCRYPTAB_FILE | while read line; do
        #Читаем по строке из файла с параметрами монтирования
            if SetStruct "$line"
            then
            # Если строчка успешно распарсилась
                if [ "$CRYPT_MOUNTPOINT" = "swap" ]
                then
                # Если в строке описан свап файл
                    DEBUG "Deactivating swap /dev/mapper/$CRYPT_NAME"
                    # Отключаем подкачку на описанном разделе
                    swapoff /dev/mapper/$CRYPT_NAME
    #               swapoff -UUID=`cat $KEYCRYPTAB_DIR/swaps/$CRYPT_UUID`
                else
                # Иначе (в строке описан обычный раздел)
                    # Узнаём смонтирован он или нет
                    chkmnt=`mount | grep "$CRYPT_NAME"`
                    if [ "$chkmnt" != "" ]
                    then
                    # Если смонтирован
                        # размонтируем
                        echo "Unmounting encrypted volume"
                        DEBUG "/dev/mapper/$CRYPT_NAME"
                        umount $UMOUNT_FLAG /dev/mapper/$CRYPT_NAME
                    fi
                fi
                if [ -e "/dev/mapper/$CRYPT_NAME" ]
                then
                # Если описанный раздел подключен 
                    echo "Closing encrypted volume"
                    DEBUG "/dev/mapper/$CRYPT_NAME"
                    # Отключаем
                    cryptsetup luksClose $CRYPT_NAME
                fi
            fi
        done
        ;;
     restart|reload)
         do_stop
         do_start
         ;;
     force-reload)
        UMOUNT_FLAG="-f"
        do_stop
        do_start
        ;;
     *)
         echo "Usage: $1 {start|stop|restart|reload|force-reload}"
         echo "Actions 'stop', 'restart', 'reload' and  'force-reload' will unmount"
         echo "all encrypted disk partitions, including partition containing swap"
         echo "To mount the additional partitions without unount already mounted,"
         echo "run $1 script with the parameter 'start' again"
         exit 1
         ;;
     esac
    
    
  2. Файл keycryptab

    
    #All swap partition is required for the mount point "swap"
    #key file name for swap can take only 2 values:
    #"none" (is strongly not recommended) and "random" example:
    #11111111-2222-3333-4444-555555555555 random swap1 swap
    
    #<uuid>                              <key filename> <luks name> <dotmount>
    0cf1c420-09a0-4338-85b4-df6aed780425 random         swap1       swap
    4ebecf51-4a5a-4aaf-ba97-3523129e567c keyfile.key    dtb         /var/lib/pgsql
    0feb764f-195e-487d-a0ed-1de525fb3282 bacup.key bkp /media/old
    #this file MUST contain final newline or final comment
    
  3. Файл keycryptab

    
    # Syntax:
    # <uuid> <timeout> <dotmount>
    # Example:
    # kkkkkkkk-kkkk-kkkk-kkkk-kkkkkkkkkkkk 20 /media/keys
    
    # <uuid>                             <timeout> <dotmount>
    609e85b7-5fa8-4434-9210-b8df1d4c0a66 20 /media/keys
    bc5e202a-1523-46bc-95f4-3c89f10edd27 120 /media/keys #bacup user
    #this file MUST contain final newline or final comment
    

На текущий момент решение работает уже на нескольких серверах и было адаптировано под Debian и

ArchLinux


#!/bin/bash
#Keycrypt 1.1 SmartTech 2012 модификация ArchLinux

# Этот скрипт находит сьёмные носители, содержащие файл ключа,
# подкключает и монтирует зашифрованные файловые системы и раздел подкачки

#Инклайды функций для корректного отображения состояния на загрузочном экране
. /etc/rc.conf
. /etc/rc.d/functions

KEYCRYPTAB_DIR=/etc/keycrypt
# Рабочая директория

KEYCRYPTAB_FILE=$KEYCRYPTAB_DIR/keycryptab
# Файл параметров разделов
# Этот файл содержит параметры монтирования всех разделов
# которые необходимо монтировать при помощи ключей
# В этом файле обязательно должен быть финальный перевод строки или комментарий в конце
# Синтаксис:
# <uuid раздела> <имя файла ключа> <имя радела для links> <точка монтирования>
# Для раздела подкачки в качестве точки монтирования нужно указать "swap".
# Имя файла ключа для раздела подкачки может содержать всего 2 значиния: "none" и "random"
# Use the option "none" is not recommended.
# Examples:
# 11111111-2222-3333-4444-555555555555 random swap1 swap
# #safety swap partition. It no need any keyfile, but must be encrypted
# 12345678-1234-4321-1234-567890123456 harry.key harry /home/harry
# #home directory for Harry, Harry have the "harry.key" in his flash drive
# 66666666-9999-8888-7777-000000000000 ntldr public "/var/ftp"
# #publuc directory, all staff have the "ntldr" file in flash drives

KEYDRIVER_FILE=$KEYCRYPTAB_DIR/keydrv
# Файл параметров ключевого носителя
# Этот файл содержит параметры ключевых носителей
# В этом файле обязательно должен быть финальный перевод строки или комментарий в конце
# Синтаксис:
# <uuid> <timeout> <dotmount>
# Examples:
# kkkkkkkk-kkkk-kkkk-kkkk-kkkkkkkkkkkk 20 /media/keys


SWAPCLEAN_FILE=$KEYCRYPTAB_DIR/swapclean.flg
# NOTE!!! If u are not using the encription swap
# u need to run "dd if=/dev/urandom of=/u/swap/partition".
# Otherwise encryption will not make sense.
# I was include this functional on ершы sсript, but
# it will take a very long time to load OS.

DBG="on"
# Разкоментировать строку выше для вывода отладки


#=====================Begin of script=====================
# Do not edit next if you are not sure what you are doing!

UMOUNT_FLAG=""
# менять на свой страх и риск

# Функция вывода отладки, печатает переданное сообщения желтым цветом, 6 параметров (НЕ СЛОВ! слов может быть хоть миллион)
DEBUG()
{
    if [ "$DBG" = "on" ]
    then
        echo -e "E[33;40m$1 $2 $3 $4 $5 $6"; tput sgr0
    fi
}

# Функция парсит строчку с параметрами разделов и выставляет значения переменных
SetStruct()
{
str=`echo $1 | sed 's/#.*/ /g'`
n=0
    for arg in $str
    do
        let "n+=1"
        case "$n" in
        1)
            CRYPT_UUID=$arg
            ;;
        2)
            KEY_FILENAME=$arg
            ;;
        3)
            CRYPT_NAME=$arg
            ;;
        4)
            CRYPT_MOUNTPOINT=$arg
            return 0
            ;;
        *)
            DEBUG "too many arguments: $arg"
            ;;
        esac
    done
    return 1
}

# Функция парсит строчку с параметрами ключевого носителя и выставляет значения переменных
SetKey()
{
nk=0
str=`echo $1 | sed 's/#.*/ /g'`
    for arg in $str
    do
        let "nk+=1"
        case "$nk" in
        1)
            KEY_UUID=$arg
            ;;
        2)
            KEY_TIMEOUT=$arg
            if (( KEY_TIMEOUT < 0 ))
            then
                echo "Invalid key timeout for $KEY_UUID"
                KEY_TIMEOUT=1
            fi
            if (( KEY_TIMEOUT > 60 ))
            then
                echo "Invalid key timeout for $KEY_UUID"
                KEY_TIMEOUT=60
            fi
            DEBUG "KEY_TIMEOUT=$KEY_TIMEOUT"
            ;;
        3)
            KEY_MOUNTPOINT=$arg
            return 0
            ;;
        *)
            DEBUG "too many arguments: $arg"
            ;;
        esac
    done
    return 1
}

# функция монтирует ключевой носитель
PrepareKeyDrv()
{
    if [ ! -e "/dev/disk/by-uuid/$KEY_UUID" ]
    then
    #если ключевой носитель не найден
        echo -en "Waiting $KEY_TIMEOUT seconds for the key device  r"
        #ожидаем пока загрузится модуль
        for (( n = ++KEY_TIMEOUT; n ; n-- ))
        do
            if [ ! -e "/dev/disk/by-uuid/$KEY_UUID" ]
            then
                (( KEY_TIMEOUT-- ))
                echo -en "Waiting $KEY_TIMEOUT seconds for the key device  r"
            else
                DEBUG "Key device was found!"
                DEBUG $KEY_UUID
                break
            fi
            if [ "$n" = "1" ]
            then
                DEBUG "Not found a key device!"
                DEBUG "Now completing PrepareKeyDrv()"
                #не нашли, выходим
                return 1
            fi
            sleep 1 # it is not a debug! Do not comment it!
        done
    fi
    #нашли, монтируем, определяя фс по длинне uuid
    DEBUG "Mounting the key device"
    if [ ! -d "$KEY_MOUNTPOINT" ]; then
        RM_KMP=1
        mkdir $KEY_MOUNTPOINT
    fi
    uuid_l=`echo $KEY_UUID | wc -m`
    case "$uuid_l" in
        10)
            mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -t vfat -o ro
            DEBUG "fat detected"
            ;;
        17)
            mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -t ntfs-3g -o force,ro
            DEBUG "ntfs detected"
            ;;
        37)
            mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -o ro
            DEBUG `mount | grep $KEY_MOUNTPOINT `
            ;;
        *)
            DEBUG "Can not identify type of the file system on the specified uuid:"
            DEBUG "$KEY_UUID"
            mount /dev/disk/by-uuid/$KEY_UUID $KEY_MOUNTPOINT -o ro
            ;;
    esac
}

# Функция размонтирует ключевые носители для извлечения
UmountKey()
{
    DEBUG "Unmounting the key device"
    umount $1 /dev/disk/by-uuid/$KEY_UUID
    if [ "$RM_KMP" = "1" ]; then
        rmdir $KEY_MOUNTPOINT
    fi
}

# Функция подготавливает разделы для подкачки
PrepareSwap()
{
    if [ "$KEY_FILENAME" = "random" ]
    then
    # Если используем шифрованные разделы подкачки
        if [ ! -e "/dev/disk/by-uuid/$CRYPT_UUID" ]
        then
        # Если раздела нет
            # ругаемся
            DEBUG "Not found any partition for swap with UUID:"
            DEBUG "$CRYPT_UUID"
            ### Если тебе в голову придёт изменить логику проверок ДОПИШИ СЮДА ВЫХОД ИЗ ФУНКЦИИ
        else
        # Раздел существует
            DEBUG "Prepare to encripting swap"
            DEBUG "Disable all swaps"
            # Отрубаем всю подкачку (кстати, откуда она у нас ?)
            swapoff -a
            DEBUG "Regenerating new temporary key"
            # Создаём папку для временного ключа
            mkdir /tmp/key ### НЕ ВЗДУМАЙ МЕНЯТЬ ПУТЬ особенно если ты не позаботился о том чтобы ключ не записался на винт
            # Монтируем в эту папку кусочек оперативы (свап же мы отключили? Ключик на винте не окажется?)
            mount -t ramfs none /tmp/key -o maxsize=1
            # генерим ключик
            dd if=/dev/urandom of=/tmp/key/swapkey$CRYPT_UUID bs=1 count=256 &> /dev/null
            DEBUG "Configuring encrypt on the swap partition"
            # создаём на партиции шифрованный раздел
            echo "YES"|cryptsetup luksFormat /dev/disk/by-uuid/$CRYPT_UUID /tmp/key/swapkey$CRYPT_UUID --uuid=$CRYPT_UUID
            # подключаем его
            DEBUG "Openinig swap partition for operations"
            cryptsetup luksOpen /dev/disk/by-uuid/$CRYPT_UUID $CRYPT_NAME --key-file /tmp/key/swapkey$CRYPT_UUID
            # ключик уничтожаем
            DEBUG "Erasing temporary key"
            rm -r -f /tmp/key/swapkey$CRYPT_UUID
            # Размонтируем рамдиск
            umount none
            # генерируеим новый UUID для свопа (не путать с uuid luks который имеет физический раздел)
            DEBUG "Regenerating swapfs uuid"
            FS_CRYPT_UUID=`uuidgen`
            #echo $FS_CRYPT_UUID > $KEYCRYPTAB_DIR/swaps/$CRYPT_UUID
            DEBUG "Creating swap format"
            if [ -e "$SWAPCLEAN_FILE" ]
            then
            # Если в прошлый раз кто-то замонтировал раздел не шифруя забиваем его мусором чтоб стереть остатки инфы
                ### неплохо бы однако проверять это не по наличию а по отсутсвию файла, это более секьюрно, и при установке скрипт сам позаботится о том чтобы всё сделать правильно.
                echo "Swap partition was mounted unsafe, cleaning..."
                echo -e 'It may take a long time. e[31;40mDo not halt the computer! e[0m'
                S=`fdisk -s /dev/mapper/$CRYPT_NAME`
                let "S *= 1024"
                dd if=/dev/urandom | pv -s $S | dd of=/dev/mapper/$CRYPT_NAME 2> /dev/null
                rm -r -f $SWAPCLEAN_FILE
            fi
            # создаём swapfs
            mkswap -f -U $FS_CRYPT_UUID /dev/mapper/$CRYPT_NAME &> /dev/null
            # врубаем подкачку
            DEBUG "Activating swap"
            swapon -U $FS_CRYPT_UUID
        fi
    else
    # Нешифрованный свап, обычное монтирование раздела со свапом токлько много мата и специальный файлик-флаг
        if [ ! -e "/dev/disk/by-uuid/$CRYPT_UUID" ]
        then
            DEBUG "Not found any partition for swap with UUID:"
            DEBUG "$CRYPT_UUID"
        else
            chkswapt=`cat /proc/swaps | grep "$CRYPT_NAME"`
            if [ ! "$chkswapt" = "" ]
            then
                echo "Oops! e[31;40mYou already have the swap! e[0m"
                cat /proc/swaps
                echo "Сheck your 'fstab', 'cryptab' and '$KEYCRYPTAB_FILE'"
                echo "files for duplicate entries for swap partition"
                echo "In future use only one of these files to manage swaps"
                return 0
            else
                echo -e 'e[31;40mWARNING!!! e[0mThe system uses the unface way to manage swap!'
                echo 'You need to use value "random" of <key filename> on the'
                echo "$KEYCRYPTAB_FILE file for all swap partition!"
            fi
            swapoff -a
            mkswap -f -U $CRYPT_UUID /dev/disk/by-uuid/$CRYPT_UUID &> /dev/null
            swapon -U $CRYPT_UUID && echo "Remoove this file for disable cleaning swap on boot time" > $SWAPCLEAN_FILE
#           echo $CRYPT_UUID > $KEYCRYPTAB_DIR/swaps/$CRYPT_UUID
        fi
    fi
}

# Функция подключает и монтирует шифрованные разделы
PrepareVolumes()
{
    cat $KEYCRYPTAB_FILE | while read line; do
    #Читаем по строке из файла с параметрами монтирования
        if SetStruct "$line"
        then
        # Если строчка успешно распарсилась
            if [ ! -e "/dev/disk/by-uuid/$CRYPT_UUID" ]
            then
            # Если такого раздела нет
                # Ругаемся, переходим к следующему
                DEBUG "Not found encrypted partition with UUID:"
                DEBUG "$CRYPT_UUID"
            else
            # Если раздел существует
                if [[ "$CRYPT_MOUNTPOINT" = "swap" && "$1" = "swaps" ]]
                then
                # Если раздел для подкачки и процедура запущена с параметром "swaps"
                    # Запускаем для монтирования свап раздела специальную процедуру
                    PrepareSwap
                else
                # Если это обычный раздел
                    if [[ "$CRYPT_MOUNTPOINT" != "swap" && "$1" != "swaps" ]] ### Уберёшь первое условие, сломаю руку! Ибо сюда может попасть свап раздел и в лучшем случае получишь срач на вывод ошибок от моунта, в худшекм останешься без свопа или что ещё хуже с нешифрованным свопом.
                    then
                    # И если процедура не запущена с параметром "swaps"
                        # Подключаем шифрованный раздел
                        DEBUG "Opening encrypted volume"
                        cryptsetup luksOpen /dev/disk/by-uuid/$CRYPT_UUID $CRYPT_NAME --key-file $KEY_MOUNTPOINT/$KEY_FILENAME
                        if [ -e "/dev/mapper/$CRYPT_NAME" ]
                        then
                        # Если подключился
                            # Создаём точку монтирования и монтируем
                            DEBUG "Mounting encrypted volume"
                            if [ ! -d "$CRYPT_MOUNTPOINT" ]; then
                                mkdir $CRYPT_MOUNTPOINT
                            fi
                            mount /dev/mapper/$CRYPT_NAME $CRYPT_MOUNTPOINT
                        fi
                    fi
                fi
            fi
        fi
    done
}


 case "$1" in
start)
# Эта часть выполняется при запуске скрипта с параметром start
# Например при запуске компьютера
    stat_busy "Preparing encrypted partitions"
#     if [ -d "$KEYCRYPTAB_DIR/swaps/" ]
#     then
#         rm -r -f $KEYCRYPTAB_DIR/swaps/*.*
#     else
#         mkdir --parents $KEYCRYPTAB_DIR/swaps/
#     fi
    cat $KEYDRIVER_FILE | while read line; do
    #Читаем по строке из файла ключей
        if SetKey "$line"
        then
        # Если строчка успешно распарсилась
            if PrepareKeyDrv
            then
            # Если ключевой носитель удалось смонтировать
                # Монтируем шифрованные разделы
                PrepareVolumes
                # Размонтируем ключевой носитель
                UmountKey
            fi
        fi
    done

    #Монтируем разделы подкачки
    PrepareVolumes swaps
    DEBUG "Now comleting"
    if [ $? -gt 0 ]; then
      stat_fail
    else
      stat_done
    fi
    add_daemon internet
    ;;
 stop)
# Эта часть выполняется при запуске скрипта с параметром stор
# Например при отгрузке OS
    cat $KEYCRYPTAB_FILE | while read line; do
    #Читаем по строке из файла с параметрами монтирования
        if SetStruct "$line"
        then
        # Если строчка успешно распарсилась
            if [ "$CRYPT_MOUNTPOINT" = "swap" ]
            then
            # Если в строке описан свап файл
                DEBUG "Deactivating swap /dev/mapper/$CRYPT_NAME"
                # Отключаем подкачку на описанном разделе
                swapoff /dev/mapper/$CRYPT_NAME
#               swapoff -UUID=`cat $KEYCRYPTAB_DIR/swaps/$CRYPT_UUID`
            else
            # Иначе (в строке описан обычный раздел)
                # Узнаём смонтирован он или нет
                chkmnt=`mount | grep "$CRYPT_NAME"`
                if [ "$chkmnt" != "" ]
                then
                # Если смонтирован
                    # размонтируем
                    DEBUG "Unmounting encrypted volume"
                    DEBUG "/dev/mapper/$CRYPT_NAME"
                    umount $UMOUNT_FLAG /dev/mapper/$CRYPT_NAME
                fi
            fi
            if [ -e "/dev/mapper/$CRYPT_NAME" ]
            then
            # Если описанный раздел подключен 
                DEBUG "Closing encrypted volume"
                DEBUG "/dev/mapper/$CRYPT_NAME"
                # Отключаем
                cryptsetup luksClose $CRYPT_NAME
            fi
        fi
    done
    rm_daemon internet
    if [ $? -gt 0 ]; then
       stat_fail
    else
       stat_done
    fi
    ;;
 restart|reload)
     do_stop
     do_start
     ;;
 force-reload)
    UMOUNT_FLAG="-f"
    do_stop
    do_start
    ;;
 *)
     echo "Usage: $1 {start|stop|restart|reload|force-reload}"
     echo "Actions 'stop', 'restart', 'reload' and  'force-reload' will unmount"
     echo "all encrypted disk partitions, including partition containing swap"
     echo "To mount the additional partitions without unount already mounted,"
     echo "run $1 script with the parameter 'start' again"
     exit 1
     ;;
 esac

(ещё до того как он обзавелся systemd).

Автор: kin63camapa

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js