Именно с такой задачей я столкнулся несколько дней назад на работе. Требовалось организовать поддержку ГОСТовых алгоритмов шифрования в CentOS/Redhat 7 , чтобы работать с сайтом госуслуг. Сразу оговорюсь, поддержка была добавлена в OpenSSL, curl и PHP 7.1 путем сборки из исходников .
Поддержка ГОСТ алгоритмов осуществляется с помощью модуля GOST Engine , добавляемого в OpenSSL .
Инструкция является сборной солянкой из найденного в интернете и адаптирована под CentOS 7, права на оригинальные инструкции, части которых были использованы принадлежат их авторам.
Исходные данные
Начнем с того, что у меня было:
- 1 сервер на CentOS 7 (площадка разработки)
- 3 сервера на Redhat 7 (боевые площадки)
- OpenSSL 1.0.2k - последняя доступная версия в репозиториях Centos/Redhat 7
- PHP 7.1.33 (FPM) из репозитория Remi (в режиме MultiVersion )
- Подключены репозитории EPEL/Remi
- На Redhat серверах ограничен доступ внешним ресурсам, поэтому часть того, что на CentOS 7 была скачана wget`ом, тут была загружена с помощью копирования скаченного архива на сервер.
- В PHP была добавлена поддержка БД MySQL и PostgreSQL (MariaDB 10 и PostgreSQL 10 соответственно).
Общий план работ
- Создание резервной копии директории /etc
- Обновить ОС на сервере
- Сборка OpenSSL 1.1.х
- Сборка GOST Engine
- Сборка Curl
- Сборка PHP
- Фиксация версий
Сборка OpenSSL
Для начала установим необходимые компоненты, а именно: подключим репозиторий EPEL (если не подключен), cmake3
yum install cmake3 wget curl unzip yum-utils gcc
Теперь удалим установленный OpenSSL, он мешает сборке, позже мы его вернем, для обратной совместимости. Предварительно сделайте резервные копии конфигов из /etc т.к. удалятся все зависимые пакеты (у меня удалился как минимум certbot ).
yum remove openssl
Проверим, что OpenSSL удалился, попытавшись вызвать его:
openssl version
В результате должны получить ошибку, что он не установлен.
Скачиваем OpenSSL 1.1.0g в любую удобную директорию , только с ним у меня корректно все заработало и собралось. Если доступ к внешним ресурсам ограничен, то загрузите архив на сервер любым доступным вам способом, имя архива должно быть openssl-1.1.0g.tar.gz
wget "https://www.openssl.org/source/openssl-1.1.0g.tar.gz" -O "openssl-1.1.0g.tar.gz"
Распаковываем загруженный архив:
tar -zxvf "openssl-1.1.0g.tar.gz"
Переходив в директорию openssl-1.1.0g
cd "openssl-1.1.0g"
Запускаем конфигурацию OpenSSL, по окончании всей сборки (последующие этапы) он будет установлен в /usr/local/ssl/
./config no-async shared --prefix=/usr/local/ssl --openssldir=/usr/local/ssl -Wl,-rpath,/usr/local/ssl/lib
Теперь запускаем сборку и после ее успеха - установку:
make && make install
Делаем симлинк на новый OpenSSL:
ln -s /usr/local/ssl/bin/openssl /usr/bin/openssl
Теперь проверим доступность OpenSSL, он должен быть доступен в /usr/bin/openssl и вызываться обычной командой openssl :
openssl version
В результате вы должны получить OpenSSL 1.1.0g
На этом сборка OpenSSL окончена, теперь добавим в него поддержку GOST шифрования.
Сборка GOST Engine
Перейдем в директорию, где будем осуществлять сборку:
cd /usr/local/src
Скачиваем архив с GOST Engin e, к сожалению у меня не заработала последняя версия, поэтому загружаем архив, с которым все успешно собиралось. Если доступ к внешним ресурсам ограничен, то загрузите архив любым доступным вам способом и именем gost-engine.zip в эту же директорию.
wget "https://github.com/gost-engine/engine/archive/3bd506dcbb835c644bd15a58f0073ae41f76cb06.zip" -O gost-engine.zip
Распаковываем архив
unzip gost-engine.zip -d ./
Переходим в директорию распакованного архива
cd "engine-3bd506dcbb835c644bd15a58f0073ae41f76cb06/"
При сборке и работе могут дублироваться сообщения о том, что GOST Engine уже загружен, поэтому спрячем это сообщение сразу в исходниках:
sed -i 's|printf("GOST engine already loaded\\n");|goto end;|' gost_eng.c
Создадим директорию build , где будем осуществлять сборку и сразу перейдем в нее:
mkdir build && cd build
Сконфигурируем систему сборки:
cmake3 -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_FLAGS='-I/usr/local/ssl/include -L/usr/local/ssl/lib' -DOPENSSL_ROOT_DIR=/usr/local/ssl -DOPENSSL_INCLUDE_DIR=/usr/local/ssl/include -DOPENSSL_LIBRARIES=/usr/local/ssl/lib ..
И теперь соберем наш GOST Engine:
cmake3 --build . --config Release
Выйдем в директорию bin выше уровнем
cd ../bin
Скопируем утилиту gost12sum в директорию /usr/local/bin/
cp gostsum gost12sum /usr/local/bin
Выйдем еще выше уровнем
cd ..
Скопируем собранный движок gost.so в директорию engines-1.1 openssl
cp bin/gost.so /usr/local/ssl/lib/engines-1.1
Добавим конфигурацию для GOST Engine в файл конфигурации OpenSSL:
sed -i '6i openssl_conf=openssl_def' /usr/local/ssl/openssl.cnf
echo "" >> /usr/local/ssl/openssl.cnf
echo "# OpenSSL default section" >> /usr/local/ssl/openssl.cnf
echo "[openssl_def]" >> /usr/local/ssl/openssl.cnf
echo "engines = engine_section" >> /usr/local/ssl/openssl.cnf
echo "" >> /usr/local/ssl/openssl.cnf
echo "# Engine scetion" >> /usr/local/ssl/openssl.cnf
echo "[engine_section]" >> /usr/local/ssl/openssl.cnf
echo "gost = gost_section" >> /usr/local/ssl/openssl.cnf
echo "" >> /usr/local/ssl/openssl.cnf
echo "# Engine gost section" >> /usr/local/ssl/openssl.cnf
echo "[gost_section]" >> /usr/local/ssl/openssl.cnf
echo "engine_id = gost" >> /usr/local/ssl/openssl.cnf
echo "dynamic_path = /usr/local/ssl/lib/engines-1.1/gost.so" >> /usr/local/ssl/openssl.cnf
echo "default_algorithms = ALL" >> /usr/local/ssl/openssl.cnf
echo "CRYPT_PARAMS = id-Gost28147-89-CryptoPro-A-ParamSet" >> /usr/local/ssl/openssl.cnf
Теперь проверим, что движок GOST подключился:
openssl engine
В результате должен появиться список из доступных движков, где будет присутствовать gost :
Проверим список доступных шифров и найдем среди них интересующий нас GOST :
openssl ciphers|tr ':' '\n'|grep GOST
В результате вы должны получить следующий список:
Как видите, теперь OpenSSL умеет работать с ГОСТ шифрами. При генерации ключа нужный гост вызывается через -newkey gost2012_256
Сборка Curl
Перейдем в директорию, где будем выполнять сборку:
cd /usr/local/src
Теперь загрузим нужную версию Curl. У меня заработало только с 7.59.0. Если доступ к внешним ресурсам ограничен, то загрузите архив любым доступным вам способом с именем curl-7.59.0.tar.gz
wget "https://curl.haxx.se/download/curl-7.59.0.tar.gz" -O "curl-7.59.0.tar.gz"
Распакуем архив
tar -zxvf "curl-7.59.0.tar.gz"
Перейдем в распакованную директорию
cd "curl-7.59.0"
Сконфигурируем Curl для работы с нужной версией OpenSSL
CPPFLAGS="-I/usr/local/ssl/include" LDFLAGS="-L/usr/local/ssl/lib -Wl,-rpath,/usr/local/ssl/lib" LD_LIBRARY_PATH=/usr/local/ssl/lib ./configure --prefix=/usr/local/curl --with-ssl=/usr/local/ssl --with-libssl-prefix=/usr/local/ssl
Теперь соберем и установим Curl:
make && make install
Переименуем оригинальный (системный) curl в curl_real :
mv /usr/bin/curl{,_real}
Сделаем симлинк на свежесобранный curl в /usr/bin/curl
ln -s /usr/local/curl/bin/curl /usr/bin/curl
Ну и проверим версию собранного curl :
curl --version
В результате вы должны получить нечто подобное:
На этом и сборку Curl мы закончили.
Теперь соберем PHP
Сборка PHP
При сборке PHP в RedHat 7 столкнулся с тем, что не все devel компоненты есть в стандартных репозиториях (в том числе EPEL). Поэтому пришлось скачать их с помощью другой CentOS 7 системы и закинуть в Redhat 7 нужные rpm файлы.
Только для RHEL:
В любом доступной вам CentOS 7 системе установите yum-utils :
yum install yum-utils
После чего скачайте rpm файлы нужных библиотек:
yumdownloader bison-devel libwebp-devel libedit-devel
В результате вы получите набор rpm файлов, берите те, что оканчиваются на .x86_64.rpm и копируйте в RHEL систему любым доступным вам способом.
После чего установите эти rpm файлы:
yum localinstall *.x86_64.rpm
Общая инструкция:
Удалим devel компоненты zlib , они могут мешать сборке:
yum remove zlib-devel
Перейдем в директорию, где будем собирать PHP
cd /usr/src
В зависимости от того, какая БД вам нужна (мне пришлось собирать на Dev сервере с поддержкой MySQL и PostgreSQL, а на Prod серверах только с PostgreSQL) - установите нужные пакеты:
MySQL:
yum install libxml2 libxml2-devel bzip2-devel libmcrypt-devel libwebp-devel libjpeg-devel libpng-devel libicu-devel libpqxx-devel readline-devel bison-devel bison systemd-devel pkgconfig autoconf bison re2c libicu-devel e2fsprogs-devel mariadb-devel readline libedit-devel
PostgreSQL:
yum install libxml2 libxml2-devel bzip2-devel libmcrypt-devel libwebp-devel libjpeg-devel libpng-devel libicu-devel libpqxx-devel readline-devel bison-devel bison systemd-devel pkgconfig autoconf bison re2c libicu-devel e2fsprogs-devel readline libedit-devel
Удалим devel компоненты openssl , если они установлены:
yum remove openssl-devel
Создадим директорию, где позже расположим конфигурационные файлы модулей PHP:
mkdir -p /usr/local/php/etc/conf.d
Загрузим PHP 7.1.33 , именно эта версия у меня была установлена из Remi. Если доступ к внешним ресурсам ограничен, то загрузите архив любым доступным для вас способом в директорию /usr/src и именем php-7.1.33.tar.gz
wget "https://github.com/php/php-src/archive/php-7.1.33.tar.gz" -O php-7.1.33.tar.gz
Распакуем полученный архив
tar -zxvf php-7.1.33.tar.gz
И перейдем в распакованную директорию:
cd php-src-php-7.1.33/
Сделаем первичную конфигурацию:
./buildconf --force
Теперь в зависимости от поддержки нужных БД, сконфигурируем PHP. Если нужна поддержка обеих версий - добавьте разница одного варианта в другой, и не забудьте установить пакеты для обеих БД
MySQL:
./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-config-file-scan-dir=/usr/local/php/etc/conf.d --enable-fpm --with-fpm-systemd --with-openssl --with-openssl-dir=/usr/local/ssl --with-pcre-regex --with-pcre-jit --with-zlib --enable-bcmath --with-bz2 --enable-calendar --with-curl=/usr/local/curl --enable-exif --with-gd --enable-intl --enable-mbstring --enable-pcntl --with-pdo-pgsql --enable-soap --enable-sockets --with-xmlrpc --enable-zip --with-webp-dir --with-jpeg-dir --with-png-dir --enable-cli -enable-ftp --with-libedit --with-iconv --enable-json --with-mcrypt --enable-opcache --enable-pdo --with-pgsql --enable-xml --enable-xmlreader --enable-xmlwriter --with-pdo-pgsql=/usr/pgsql-10/bin --with-pgsql=/usr/pgsql-10/bin --with-mysqli --with-pdo-mysql
PostgreSQL:
./configure --prefix=/usr/local/php --with-config-file-path=/usr/local/php/etc --with-config-file-scan-dir=/usr/local/php/etc/conf.d --enable-fpm --with-fpm-systemd --with-openssl --with-openssl-dir=/usr/local/ssl --with-pcre-regex --with-pcre-jit --with-zlib --enable-bcmath --with-bz2 --enable-calendar --with-curl=/usr/local/curl --enable-exif --with-gd --enable-intl --enable-mbstring --enable-pcntl --with-pdo-pgsql --enable-soap --enable-sockets --with-xmlrpc --enable-zip --with-webp-dir --with-jpeg-dir --with-png-dir --enable-cli -enable-ftp --with-libedit --with-iconv --enable-json --with-mcrypt --enable-opcache --enable-pdo --with-pgsql --enable-xml --enable-xmlreader --enable-xmlwriter --with-pdo-pgsql=/usr/pgsql-10/bin --with-pgsql=/usr/pgsql-10/bin
Очищаем директорию сборки
make clean
Далее есть один нюанс, если доступ к внешним ресурсам у вас ограничен, то последующие шаги у вас не завершатся успехом т.к. PHP пытается скачать PEAR из внешних источников.
Чтобы эту проблему решить - скачаем и положим нужный файл, в нужное место.
Загрузите файл с именем install-pear-nozlib.phar
https://pear.php.net/go-pear.phar
После чего загрузите его в директорию /usr/src/php-src-php-7.1.33/pear / любым доступным вам способом.
Приступим непосредственно к сборке и установке:
make && make install
Процесс довольно длительный, после окончания процесса, создадим симлинки на новую версию PHP
Приведу список команд без комментариев, но общий принцип таков, что если файл есть - переименовываем его с суффиксом _old
ln -s /usr/local/php/bin/pear /usr/bin/pear
ln -s /usr/local/php/bin/peardev /usr/bin/peardev
ln -s /usr/local/php/bin/pecl /usr/bin/pecl
mv /usr/bin/phar{,_old}
ln -s /usr/local/php/bin/phar /usr/bin/phar
mv /usr/bin/php{,_old}
ln -s /usr/local/php/bin/php /usr/bin/php
mv /usr/bin/php-cgi{,_old}
ln -s /usr/local/php/bin/php-cgi /usr/bin/php-cgi
mv /usr/bin/php-config{,_old}
ln -s /usr/local/php/bin/php-config /usr/bin/php-config
ln -s /usr/local/php/bin/phpdbg /usr/bin/phpdbg
mv /usr/bin/phpize{,_old}
ln -s /usr/local/php/bin/phpize /usr/bin/phpize
ln -s /usr/local/php/sbin/php-fpm /usr/sbin/php-fpm
Файлы конфигурации (php.ini и php-fpm.conf ) я брал из Remi
cp /etc/opt/remi/php71/php-fpm.conf /usr/local/php/etc/
cp /etc/opt/remi/php71/php.ini /usr/local/php/etc/
И подключим OpCache модуль:
echo "zend_extension=opcache.so" >> /usr/local/php/etc/conf.d/modules.ini
Сами конфиги пулов у меня лежат по иному пути, а т.к. я скопировал старый php-fpm.conf - то и новый php-fpm запустит их оттуда же.
Теперь создадим Systemd сервис для нашего php-fpm , для этого создайте любым удобным для вас редактором файл /usr/lib/systemd/system/php-fpm.service
vi /usr/lib/systemd/system/php-fpm.service
И заполните его следующим:
[Unit]
Description=The PHP FastCGI Process Manager
After=syslog.target network.target
[Service]
Type=simple
PIDFile=/run/php-fpm/php-fpm.pid
ExecStart=/usr/sbin/php-fpm --nodaemonize --fpm-config /usr/local/php/etc/php-fpm.conf
ExecReload=/bin/kill -USR2 $MAINPID
[Install]
WantedBy=multi-user.target
Перезагрузите список демонов:
systemctl daemon-reload
Теперь можно останавливать старый FPM и запускать новый. Старый у меня был доступен под именем php71-php-fpm , а новый просто php-fpm :
systemctl stop php71-php-fpm && systemctl start php-fpm
И добавим/исключим в автозагрузку сервисы:
systemctl enable php-fpm && systemctl disable php71-php-fpm
На этом основная часть закончена. Теперь не менее важный этап - установка Openssl (да, того самого, из репозитория) и блокировка обновлений.
Установим снова openssl, для обратной совместимости:
yum install openssl
При установке openssl подменит наш симлинк, поэтому вернем всё к нашей версии:
mv /usr/bin/openssl{,_real}
И восстановим симлинк:
ln -s /usr/local/ssl/bin/openssl /usr/bin/openssl
Теперь зафиксируем версии openssl и curl, чтобы они не начали обновляться. От PHP из Remi мы уже не зависим, поэтому не блокируем.
Установите модуль yum - versionlock
yum install yum-plugin-versionlock
Заблокируем curl
yum versionlock curl-*
Заблокируем OpenSSL
yum versionlock openssl
Важное замечание по OpenSSL
После пары дней работы заметил, что начал при работе с сервисами (использующие SSL/TLS) ошибку вроде:
OpenSSL Error messages: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
Думал где-то в коде проблема, а оказалось в другом. OpenSSL хранит некоторое количество сертификатов (корневые) и при сборке из исходников они не подтягиваются. Т.к. у нас установлен openssl в любом случае (системный), то актуальный список сертификатов у нас есть, а значит его мы можем симлинками подкинуть в нашу версию openssl. Приведу несколько команд, без комментариев. Сутя такая, что удаляем пустые папки и на их место кидаем симлинки.
rm -rf /usr/local/ssl/{certs,private}
ln -s /etc/pki/tls/certs /usr/local/ssl/certs
ln -s /etc/pki/tls/private /usr/local/ssl/private
ln -s /etc/pki/tls/cert.pem /usr/local/ssl/cert.pem
Заключение
Т.к. мне требовались конкретные версии PHP и собралось оно только на определенных версиях всех перечисленных выше компонентов - то привел инструкцию как оно точно собирается, не исключено, что и на других версиях оно соберется. Другие версии сам не пробовал т.к. требовалось запустить сервис клиента как можно скорее (как обычно у клиента всё в последний день).
Знаю, что это всё можно было оптимизировать, как-то систематизировать, но нужно было решить задачу в кратчайшие сроки, а найденные в интернете инструкции были слишком разрознены, либо сводились к использованию только в docker, либо только в Deb системах.
Инструкция в общем плане подходит и для более свежих версий PHP (с теми же версиями openssl/gost/curl), но придется повозиться с зависимостями и конфигурацией.
Итого имеем: Openssl 1.1.0g, Gost Engine, Curl 7.59.0, PHP 7.1.33
Надеюсь статья кому-то поможет :)
Статья написана для моего блога: https://cyber01.ru/kak-dobavit-podderzhku-gost-34-10-2012-v-centos-redhat-7/