Pull to refresh

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

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


Примерно 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
остановил демон postgresql, и помощи программы 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 что могло привести к непредсказуемым последствиям так как файлы базы данных монтировались прямо во время работы демона.

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

В результате был создан специальный стартовый 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. Файл keydrv
    
    # 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 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).
Tags:
Hubs:
Total votes 9: ↑6 and ↓3+3
Comments6

Articles