Двухфакторная Авторизация на Linux сервере / Хабр

Введение:

Потребовалось мне тут как-то написать небольшой API, в котором необходимо было помимо обычных запросов принимать запросы с «высокой степенью секретности».

Не я первый с этим столкнулся и мир давно уже использует для таких вещей

Поскольку на моём сервере используется nginx, то был установлен модуль SSLГугл не выдал ни одного работоспособного howto, но информация в сети есть по частям.

Итак, пошаговое руководство по настройке nginx на авторизацию клиентов через SSL-сертификаты.

Внимание! В статье для примера используются самоподписанные сертификаты!

Перед стартом создадим папку в конфиге nginx, где будут плоды наших трудов:

cd /path/to/nginx/config/
mkdir ssl && cd ssl

1 Подготовка CA

Создадим конфигnano ca.config

со следующим содержимым:

[ ca ]
default_ca = CA_CLIENT # При подписи сертификатов # использовать секцию CA_CLIENT

[ CA_CLIENT ]
dir = ./db # Каталог для служебных файлов
certs = $dir/certs # Каталог для сертификатов
new_certs_dir = $dir/newcerts # Каталог для новых сертификатов

database = $dir/index.txt # Файл с базой данных подписанных сертификатов
serial = $dir/serial # Файл содержащий серийный номер сертификата (в шестнадцатеричном формате)
certificate = ./ca.crt # Файл сертификата CA
private_key = ./ca.key # Файл закрытого ключа CA

default_days = 365 # Срок действия подписываемого сертификата
default_crl_days = 7 # Срок действия CRL
default_md = md5 # Алгоритм подписи

policy = policy_anything # Название секции с описанием политики в отношении данных сертификата

[ policy_anything ]
countryName = optional # Поля optional - не обязательны, supplied - обязательны
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional

Далее надо подготовить структуру каталогов и файлов, соответствующую описанной в конфигурационном файле

2. Создание клиентского закрытого ключа и запроса на сертификат (CSR)

Для создания подписанного клиентского сертификата предварительно необходимо создать запрос на сертификат, для его последующей подписи. Аргументы команды полностью аналогичны аргументам использовавшимся при создании самоподписанного доверенного сертификата, но отсутствует параметр -x509.

3. Подпись запроса на сертификат (CSR) с помощью доверенного сертификата (CA).

При подписи запроса используются параметры заданные в файле ca.config

openssl ca -config ca.config -in client01.csr -out client01.crt -batch

В результате выполнения команды появится файл клиентского сертификата client01.crt.

Для создания следующих сертификатов нужно повторять эти два шага.

4. Создание сертификата в формате PKCS#12 для браузера клиента

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

Запароленный файл PKCS#12 надо скормить браузеру, чтобы он смог посещать ваш сайт.

openssl pkcs12 -export -in client01.crt -inkey client01.key -certfile ca.crt -out client01.p12 -passout pass:q1w2e3

Абстрактные сокеты linux

Для взаимодействия с сервером мы будем использовать вариант UNIX domain sockets — abstract namespace sockets. В отличие от сетевых сокетов, в случае с локальными сокетами UNIX ядро знает, какой процесс подключается к сокету, и знает все о пользователе (создателе) процесса, что и позволяет переиспользовать системную аутентификацию.

В классической реализации сокеты UNIX представляют собой файлы. Это позволяет применить к ним права доступа и запретить пользователям или группам подключаться к ним, но более сложную модель привилегий на этом не построить.

РЕКОМЕНДУЕМ:
Лучший редактор бинарных файлов для Windows

Основной недостаток классической реализации — опция
SO_REUSEADDR для них не работает.

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

Для решения этой проблемы в Linux существуют так называемые abstract namespace sockets. По своей сути они идентичны традиционным сокетам UNIX, но не являются файлами и автоматически исчезают с завершением процесса, который их создал. К ним также неприменимы обычные права доступа, и авторизация остается на совести приложения — но мы ведь к этому и стремимся.

Чтобы создать абстрактный сокет, нужно добавить в начало его «пути к файлу» нулевой байт. В остальном все так же, как с обычными.

Добавляем авторизацию

Теперь переходим к получению данных о подключившемся процессе через
SO_PEERCRED. Для традиционных сокетов и абстрактных она работает одинаково.

Формально, чтобы включить эту опцию, нужно поставить сокету флаг
SO_PASSCRED=1. На практике из Python 3.7 на ядре 5.0.x работает и без его явной установки, но лишним не будет.

Прочитать данные о клиенте можно с помощью функции
socket.getsockopt с флагом
SO_PEERCRED.

Эта функция вернет нам объект типа
bytes, из которого еще нужно извлечь информацию.

Увы, встроенной функциональности для этого в Python нет, поэтому разбирать придется самим. Из
man7unix можно узнать, что она возвращает запись типа
ucred из
<sys/socket.h>:

Стандарт POSIX не определяет размер этих типов, но на практике в Linux они все — 32-разрядные знаковые целые. Таким образом, мы можем разобрать ее на части с помощью
struct.unpack(‘iii’,cred) (не забудь добавить
import struct).

РЕКОМЕНДУЕМ:
Автоматизация системы мониторинга с помощью Zabbix LLD

Модифицируем основной цикл нашего сервера:

Теперь, если запустить сервер и подключиться к нему, мы увидим картину вроде такой:

Уже неплохо. Осталось только сравнить идентификатор группы (GID) с желаемой группой
wheel.

Использованное по


Ubuntu Server 10.10 (Linux 2.6.35-22-server #35-Ubuntu SMP x86_64 GNU/Linux)

nginx 0.9.3

OpenSSL 0.9.8o 01 Jun 2022

Конфиг nginx

listen *:443;
ssl on;
ssl_certificate /path/to/nginx/ssl/server.crt;
ssl_certificate_key /path/to/nginx/ssl/server.nopass.key;
ssl_client_certificate /path/to/nginx/ssl/ca.crt;
ssl_verify_client on;

keepalive_timeout 70;
fastcgi_param SSL_VERIFIED $ssl_client_verify;
fastcgi_param SSL_CLIENT_SERIAL $ssl_client_serial;
fastcgi_param SSL_CLIENT_CERT $ssl_client_cert;
fastcgi_param SSL_DN $ssl_client_s_dn;

Настройка авторизации apache

Итак, необходимый файл паролей готов. Теперь нужно настроить Apache для проверки этого файла перед обслуживанием закрытого контента. Это можно сделать двумя способами.

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

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

Выберите наиболее подходящий способ настройки и следуйте инструкциям соответствующего раздела.

Настройка авторизации при помощи файла .htaccess

Для начала нужно настроить Apache для поддержки файлов .htaccess. Откройте конфигурации Apache:

sudo nano /etc/apache2/apache2.conf

Найдите блок <Directory> каталога /var/www (как вы понимаете, это настройки каталога document root). Включите поддержку файлов .htaccess, заменив значение директивы AllowOverride на All.

. . .<Directory /var/www/>Options Indexes FollowSymLinksAllowOverride AllRequire all granted</Directory>. . .

Сохраните и закройте файл.

Затем нужно добавить файл .htaccess в каталог, доступ к которому нужно ограничить. Опять же, в примере доступ будет ограничен к каталогу document root, /var/www/html (то есть ко всему сайту). Чтобы ограничить доступ к другому каталогу, внесите в код соответствующие поправки.

sudo nano /var/www/html/.htaccess

Настройка авторизации через виртуальный хост

Откройте файл виртуального хоста сайта, доступ к которому нужно ограничить. В данном примере используется стандартный файл 000-default.conf, содержащий виртуальный хост по умолчанию.

sudo nano /etc/apache2/sites-enabled/000-default.conf

Раскомментированный файл выглядит так:

Переносимость

К сожалению,
SO_PEERCRED не стандартизована в POSIX, и детали реализации в разных системах несколько отличаются.

В частности, OpenBSD использует другой порядок значений: uid, gid, pid, а не
pid,uid,gid.

Другие системы используют похожие по смыслу, но отличающиеся в реализации опции, например
SCM_CRED в FreeBSD.

В самом Linux также есть механизм
SCM_CREDENTIALS, который позволяет передавать любой UID и GID, на которые у пользователя есть права.

РЕКОМЕНДУЕМ:
Как написать веб-приложение устойчивое к ботнетам

Универсальную библиотеку, которая скрывала бы эти различия от пользователя и работала одинаково на всех системах, я не нашел, но частичные реализации для разных языков отыскать вполне можно.

Что интересно, относительно переносимая
getpeereid(), которая возвращает только UID и GID, в стандартную библиотеку Python и многих других языков не входит. Но если задаться целью, все реализуемо.

Пишем сервер

Для начала мы напишем основу для сервера, пока без авторизации. Чтобы не писать разбор сообщений, мы сделаем всего две команды без аргументов:
read (вернуть значение счетчика) и
inc (увеличить счетчик на единицу).

Сокет мы назовем
counter-server.

Соответственно, путь его будет ‘counter-server’.

Попробуем запустить его:

Перейдем в другую консоль и попробуем подключиться с помощью
socat.

В случае с обычным stream-сокетом протокол был бы
UNIX-CONNECT, но поскольку наш — «необычный», нужен
ABSTRACT-CONNECT.

Полезные ссылки

Надеюсь, был кому-то полезен.

P.S. Этот пост был моей первой публикацией 16 января 2022 года, старая копия удалена (по желанию администрации сайта и потому, что она не была привязана к моему аккаунту).

Получаем информацию о группах

Эта часть самая простая и легко выполняется средствами стандартной библиотеки Python.

Функции для этих целей находятся в модулях
pwd и
grp.

Имена функций и возвращаемые словари совпадают с POSIX вплоть до названий полей, хотя в Python с его поддержкой пространств имен
grp_gid и подобное выглядит немного странно.

Для примера мы используем группу администраторов по умолчанию в системах на основе Red Hat, но на ее месте могла быть любая группа.

Перепишем обработку команды
inc с проверкой прав пользователя.

Поскольку по умолчанию процесс получает GID основной (первой) группы пользователя, для удобства в ущерб производительности мы получим имя пользователя по его UID с помощью
getpwuid и будем проверять наличие этого имени в группе (поле
gr_mem в словаре, который возвращает
grp.getgrnam):

Теперь пользователь будет получать сообщение об ошибке, если не состоит в группе
wheel:

Создание файла паролей

Теперь на сервере доступна команда htpasswd, которая позволяет создать файл паролей, необходимый серверу Apache для авторизации пользователей. Создайте скрытый файл .htpasswd в каталоге /etc/apache2.

При первом запуске данной утилиты нужно использовать флаг –c, который и создаст необходимый файл. Укажите имя пользователя в конце команды, чтобы создать новую запись в файле:

sudo htpasswd -c /etc/apache2/.htpasswd 8host

Примечание: Замените условное имя пользователяapache настройка авторизации 8host своим именем.

Программа предложит создать и подтвердить пароль для этого пользователя.

Чтобы добавить других пользователей в файл паролей, используйте команду htpasswd без флага –с:

Тестирование авторизации

Чтобы убедиться, что требуемые каталоги защищены паролем, попробуйте получить доступ к закрытому контенту в веб-браузере. На экране должна появиться форма авторизации, предлагающая указать имя пользователя и пароль:

Требования

Для выполнения руководства нужен аккаунт не-root пользователя с правами sudo. Чтобы создать такой аккаунт, обратитесь к этому руководству.

Установка зависимостей

Ставить google-authenticator надо из исходников, мы же не доверяем левым PPA? Для этого потребуется git, build-essential, libpam0g-dev, checkinstall.

Установка утилит apache

Чтобы создать файл для хранения паролей, понадобится утилита htpasswd. Она входит в пакет apache2-utils, который можно найти в репозитории Ubuntu.

Обновите список пакетов и установите необходимые утилиты и сервер Apache2 при помощи следующей команды:

sudo apt-get updatesudo apt-get install apache2 apache2-utils

Шаг 1. создание собственного самоподписанного доверенного сертификата.

Собственный доверенный сертификат (Certificate Authority или CA) необходим для подписи клиентских сертификатов и для их проверки при авторизации клиента веб-сервером.С помощью приведенной ниже команды создается закрытый ключ и самоподписанный сертификат.

Шаг 2. сертификат сервера

Создадим сертификат для nginx и запрос для него

openssl genrsa -des3 -out server.key 1024
openssl req -new -key server.key -out server.csr

Подпишем сертификат нашей же собственной подписью

openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out server.crt

Чтобы nginx при перезагрузке не спрашивал пароль, сделаем для него беспарольную копию сертификата

openssl rsa -in server.key -out server.nopass.key

Заключение

Теперь контент сайта защищён с помощью пароля. Имейте в виду, что защиту паролем следует комбинировать с шифрованием SSL, в противном случае учетные данные будут передаваться  на сервер в виде простого текста, а это очень серьёзная уязвимость для безопасности.

Примечание: Чтобы узнать, как создать SSL-сертификат для Apache, читайте данное руководство.

Tags:

Похожее:  Неприступный почтовый сервер, или жизнь без спама / Хабр

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *