Архив рубрики: UNIX

Аналог IncludeOptional в веб-сервере nginx

Современные панели управления для веб-серверов, призванные облегчить администрирование оных (либо используемые в целях продажи виртуального хостинга) все еще не позволяют гибко настраивать nginx для каждого аккаунта через веб-интерфейс. Оно и понятно. Возможных настроек существует просто огромное количество и делать для всего этого веб-интерфейс ради каких-то 0,5% пользователей, которым не хватает стандартных настроек — бессмысленно. В качестве лайфхака некоторые панели позволяют администраторам серверов вписывать для конкретных хостов настройки в отдельные файлы, которые обычно остаются пустыми, но при этом все равно создаются в файловой системе. Что делать, если кастомные настройки нужны, а плодить пустые файлы не хочется?

В веб-сервере Apache для этого существовала специальная директива IncludeOptional, которая попросту игнорировалась в случае, если такого файла не было в файловой системе, либо если использовались маски для файлов и ни один файл не попал под маску.

К счастью, разработчики веб-сервера nginx поступили дальновиднее и просто позволили включать директивой include файлы с маской. При этом, естественно, никаких ошибок при отсутствии файлов, попавших под маску тоже не генерируется. Но что делать, если вы хотите включить конкретный файл и не хотите «случайного» включения туда «левого» файла по маске? Использовать небольшую хитрость — задать имя файла в виде include /usr/local/etc/nginx/optional_config_file.con[f]. Т.е. мы якобы задаем маску, но под эту маску попадает только один файл. При этом, в случае отсутствия файла никаких ошибок не появится.

Route-based IPSEC VPN между Ubuntu 18.04 (libreswan) и Cisco Router

В корпоративном секторе, особенно в больших компаниях с более, чем одним офисом зачастую возникает необходимо в site-to-site VPN-соединениях. Конечно, большинство провайдеров предлагает вам «прямую связь» через их оборудование. Но как-то так сложилось, что надежность этого решения зависит только от самого провайдера и из-за некорректных действий одного-единственного админа может зависеть целостность вашей сети. Такие объединения сетей частенько выполняются просто на уровне отдельного тегированного VLAN. И если кто-то внезапно (вполне может быть по неосторожности) скрестит вашу сеть с другими или просто снимет теги на портах — вы рискуете получить в свою сеть трафик от многих клиентов провайдера, а они, в свою очередь, получат беспрепятственный доступ в вашу сеть. Именно по этой причине доверять стоит только собственноручно поднятому IPSEC VPN. Если что-то пойдет не так — VPN просто развалится и сети будут изолированы друг от друга.

В каких случаях Route-based VPN удобнее, чем обычный site-to-site? Например, если вы хотите предоставить N хостам из одной сети доступ к ресурсам в другой сети, но создавать для этого клиентские соединения неудобно. Также, если у вас много подсетей с одной из сторон (или с обеих) и вы не хотите плодить IPSEC SA (как произойдет в случае обычного Policy-based IPSEC VPN). Или, например, вы хотите пробросить весь трафик нескольких клиентов через роутер в другой стране. Во всех этих случаях полезно и удобно будет использовать Route-based VPN.

Итак, начнем с оборудования. В моем случае это был Cisco Router серии ASR с одной стороны и сервер с Ubuntu 18.04 с другой. Цель — пустить трафик N клиентов в интернет через удаленный сервер. Начнем с Ubuntu:

  1.  Установим libreswan.
    apt-get install libreswan
  2. Разрешим форвардинг пакетов и отключим редиректы.
    echo "net.ipv4.ip_forward = 1" | tee -a /etc/sysctl.conf
    echo "net.ipv4.conf.all.accept_redirects = 0" | tee -a /etc/sysctl.conf
    echo "net.ipv4.conf.all.send_redirects = 0" | tee -a /etc/sysctl.conf
    for vpn in /proc/sys/net/ipv4/conf/*; do echo 0 > $vpn/accept_redirects; echo 0 > $vpn/send_redirects; done
  3. Применим настройки.
    sysctl -p
  4. Создадим в /etc/ipsec.d/ файл с конфигурацией нашего VPN и поместим в него следующий контент:
    nano /etc/ipsec.d/route-based-ipsec-vpn.conf
    
    config setup
        protostack=netkey
    conn vpn
        authby=secret
        pfs=no
        rekey=yes
        keyingtries=3
        type=tunnel
        auto=start
        ike=aes256-sha1;modp1536
        phase2alg=aes256-sha1;modp1536
        left=11.11.11.11
        right=22.22.22.22
        leftsubnet=0.0.0.0/0
        rightsubnet=0.0.0.0/0
        mark=5/0xffffffff
        vti-interface=vti01
        vti-routing=no

    Если один из ваших роутеров находится за NAT (например left), вам необходимо заменить в директиве left адрес на внутренний адрес интерфейса (left=10.0.0.1) и добавить директиву leftid с реальным адресом (leftid=11.11.11.11).  И, конечно, пробросить порты UDP 500, 1701 и 5000 на этот роутер. В противном случае туннель не поднимется.

    Краткие пояснения по конфигурационному файлу:
    authby=secret — авторизация по паролю, а не по сертификатам;
    vti-interface=vti01 — интерфейс, который будет создан автоматически при создании туннеля;
    vti-routing=no — не создавать маршруты на интерфейс туннеля автоматически.
  5. Далее необходимо придумать сгенерировать стойкий пароль на много символов (все равно вам его запоминать не надо) и внести его в файл.
    nano /etc/ipsec.d/route-based-ipsec-vpn.secrets
    
    11.11.11.11 22.22.22.22: PSK "%908A2R5vX9O694aoxh1"
  6. Делаем ipsec restart и ipsec verify. После ipsec verify у нас должно появиться что-то типа этого:
    Verifying installed system and configuration files
    
    Version check and ipsec on-path                         [OK]
    Libreswan 3.23 (netkey) on 4.15.0-1021-aws
    Checking for IPsec support in kernel                    [OK]
     NETKEY: Testing XFRM related proc values
             ICMP default/send_redirects                    [OK]
             ICMP default/accept_redirects                  [OK]
             XFRM larval drop                               [OK]
    Pluto ipsec.conf syntax                                 [OK]
    Two or more interfaces found, checking IP forwarding    [OK]
    Checking rp_filter                                      [ENABLED]
     /proc/sys/net/ipv4/conf/all/rp_filter                  [ENABLED]
     /proc/sys/net/ipv4/conf/default/rp_filter              [ENABLED]
     /proc/sys/net/ipv4/conf/eth0/rp_filter                 [ENABLED]
     /proc/sys/net/ipv4/conf/ip_vti0/rp_filter              [ENABLED]
      rp_filter is not fully aware of IPsec and should be disabled
    Checking that pluto is running                          [OK]
     Pluto listening for IKE on udp 500                     [OK]
     Pluto listening for IKE/NAT-T on udp 4500              [OK]
     Pluto ipsec.secret syntax                              [OK]
    Checking 'ip' command                                   [OK]
    Checking 'iptables' command                             [OK]
    Checking 'prelink' command does not interfere with FIPS [OK]
    Checking for obsolete ipsec.conf options                [OK]
    

    Если failed нигде не написано — можно жить.

  7. Заставляем трафик прятаться за айпишник нашего интерфейса при выходе наружу (в данном примере внешний интерфейс — eth0) при помощи iptables:
    iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
  8. Прописываем маршруты в нашу удаленную сеть (предположим, что на другом конце есть сети 192.168.0.0/24 и 192.168.10.0/24).
    ip route add 192.168.0.0/24 dev vti01
    ip route add 192.168.10.0/24 dev vti01
  9. На этом этапе настройка Ubuntu закончена и мы переходим к настройке Cisco ASR. Продолжение следует (линк на него будет в конце этой статьи).

При написании этого материала помог личный опыт, статья https://libreswan.org/wiki/Route-based_VPN_using_VTI и гугл.

Логирование всех запросов к mysql

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

Для этого необходимо подключиться к нашему серверу и:

  1. Создать файл лога и выдать на него права для пользователя, под которым работает mysql.
  2. Подключиться к mysql-серверу с правами root и выполнить:
    SET GLOBAL log_output = "FILE";
    
    SET GLOBAL general_log_file = "/path/to/your/logfile.log";
    
    SET GLOBAL general_log = 'ON';
  3. С этого момента в лог попадают абсолютно все запросы. Проверяем необходимый нам функционал.
  4. Если сервер довольно загружен — не забываем сразу выключить логирование:
    SET GLOBAL general_log = 'OFF';
  5. Открываем лог-файл и ищем там наши запросы.

Блокирование процесса MySQL-сервера в оперативной памяти. Memlock на FreeBSD.

Бывают случаи, когда надо запретить операционной системе сервера переносить данные определенного сервиса/приложения в swap. Т.е. все остальное можно, хоть и нежелательно, а вот какое-нибудь одно ну совсем нельзя. Для этого во всех (ну или почти во всех) современных unix системах существует системный вызов memlockall — он отвечает как раз за «блокировку» данных в оперативной памяти. Можно, конечно, долго рассуждать о том, что если swap на сервере становится нужен — пора апгрейдить сервер/снижать нагрузку на него. Так и обстоят дела в идеальном мире.

Но наш мир далек от совершенства и в ПО, в том числе системном, тоже бывают ошибки. Я столкнулся с одной пренеприятнейшей ситуацией на сервере под управлением FreeBSD 9.2, с корнем на ZFS. Ситуация состояла в том, что система никак не хотела освобождать память из-под ARC-кэша, когда она была необходима приложениям и предпочитала сбрасывать данные других приложений в swap. Все бы ничего, но самым «жрущим» процессом всегда оказывался MySQL, половину которого она и умудрялась выдавить в swap.

А потом начиналось самое интересное: в те лохматые времена, когда устанавливался этот сервер, про размещение swap на zfs никто ничего плохого не говорил. А потом выяснили, что внезапно при активном чтении/записи из свапа (например, при внезапно возросшей нагрузке с истребованием данных mysql, которые упали в свап и вытеснением других приложение туда же) может возникнуть deadlock. Выглядит это очень весело, но не в том случае, если сервер не имеет удаленного управления и стоит в далеком дата-центре, в который надо ехать, да еще и согласовав свой приезд с сотрудниками заранее. С виду сервер остается живым — отвечает на пинги, принимает пакеты на открытые порты, но не отдает никаких данных. При подключении клавиатуры бодро выкидывает в консоль сообщение о подключении девайса, но не реагирует на нее и, более того, не пишет никакие логи. Помогает только reset.

Если у вас действительно не хватает памяти на сервере, то установка «—memlock» не поможет и будет даже вредной. При исчерпании свободной памяти и отсутствии возможности «вынести» кого-нибудь в swap, система с большой долей вероятности просто убьет самый объемный процесс, которым окажется как раз mysql

Впрочем, я отвлекся. Целью этого повествования является как раз способ принуждения системы «убрать руки» от памяти, выделенной для MySQL. Для этого существует параметр запуска —memlock. Первое решение, которое приходит в голову — прописать в /etc/rc.conf строчку:

mysql_args="--memlock"

Прописываем, перезапускаем mysql и видим (с помощью запроса show variables like ‘%lock%’), что переменная locked_in_memory выставлена в OFF.

Начинаем думать и читать мануалы… Оказывается, для успешного выполнения вызова memlockall (который и выполняется при указании параметра —memlock), необходимы root-привилегии. Но мы же не хотим, чтобы сервер работал от имени root? В случае с FreeBSD без редактирования стартового скрипта mysql, к сожалению, не обойтись — пользователь ‘mysql’ туда «захардкожен» и его придется сменить ручками. Открываем /usr/local/etc/rc.d/mysql-server и вместо mysql_user=»mysql» вписываем mysql_user=»root». Далее обязательно удаляем из строки:

command_args="-c -f /usr/local/bin/mysqld_safe --defaults-extra-file=${mysql_optfile} --user=${mysql_user} --datadir=${mysql_dbdir} --pid-file=${pidfile} ${mysql_args}"

кусочек «—user=${mysql_user}». Не забываем, что в rc.conf у нас остался mysql_args=»—memlock». И, наконец, для того, чтобы пользователь после запуска сменился на mysql, указываем в файле /etc/my.cnf (по умолчанию его нет в системе) следующее:

[mysqld]
user=mysql

Важно указать это именно в /etc/my.cnf — этот файл читается первым, а mysql в целях безопасности применяет только первую указанную опцию user. После проделанных манипуляций перезапускаем mysql-server командой:

/usr/local/etc/rc.d/mysql-server restart

и проверяем переменную «locked_in_memory» — она должна быть ON. Также, вы сразу заметите увеличение количества wired memory в выводе top на объем, занятый mysql.

В моем случае это проблему решило и, как ни странно, система перестала уходить в swap — память при необходимости отбиралась у ARC, хотя других изменений не производилось.

P.S. Главное не забыть об этих изменениях после обновления mysql — скрипт может быть перезаписан в процессе обновления.

Сброс пароля root в mysql без рестарта сервиса

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

Впрочем, есть одно решение для таких случаев. Оно, конечно, не рекомендуемое (а при отсутствии должного внимания так и совсем противопоказано), но все же оно есть.

1) Запускаем второй экземпляр mysql-сервера. Как это сделать, я писал ранее здесь. Разница только в том, что сейчас не надо копировать базу mysql. Необходимо подготовить конфиги и запустить команду

mysql_install_db --datadir=/dbdata/datadir/ --user=mysql

2) Логинимся на новый сервер под учеткой root.

3) Копируем таблицу user из базы данных mysql старого сервера на новый.

4) Заставляем сервер переоткрыть таблицы командой

flush tables;

5) Меняем пароль для пользователя root:

update mysql.user set password=PASSWORD('I-will-never-forget-root-password') where user like 'root';

6) Выполняем еще раз команду flush privileges.

7) Пробуем залогиниться повторно с новым паролем.

8) Если все ок, то останавливаем второй сервер (не основной!) и копируем файлы таблицы user из базы данных в обратном направлении (т.е. на основной сервер).

9) Осталось заставить сервер перечитать таблицу с пользователями. Это можно сделать, послав процессу сигнал SIGHUP. Делается это командой

kill -1 12345

где, 12345 — pid процесса mysqld.

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