воскресенье, 22 сентября 2019 г.

Настройка BGP в Quagga

Понадобилось на работе настроить динамическую маршрутизацию по протоколу BGP, чтобы сэкономить время на настройке статических маршрутов. Поэкспериментировал для начала на виртуальных машинах на домашнем компьютере, подготовил для себя шпаргалку. На работе настраивал свои серверы в паре с сетевым администратором, который со своей стороны настраивал аппаратные маршрутизаторы. (Данила, если ты это читаешь, то передаю тебе привет!) Когда работа была выполнена, дополнил шпаргалку несколькими полезными разделами.

Через пару месяцев понадобилось вернуться к настройке BGP по ещё двум поводам. Во-первых, на одной из виртуальных машин в целях резервирования анонсы нужно было принимать сразу от двух соседних маршрутизаторов. Во-вторых, понадобилось настроить сервер, который сам в целях резервирования должен быть доступен через два соседних маршрутизатора. Конфигурацию этого сервера проверил и дополнил другой сетевой администратор. (Андрей, тебе тоже привет!) В результате добавил в шпаргалку ещё несколько пунктов.

По обыкновению выбрасываю отработанный материал в мусоркублог.

Установка пакетов

Для установки демона маршрутизации Quagga с поддержкой одного лишь протокола BGP достаточно установить только один пакет:
# apt-get install quagga-bgp
Вместе с этим пакетом будет установлен также пакет quagga-core, в котором находится демон zebra. Демон zebra отвечает за добавление маршрутов, полученных от демонов динамической маршрутизации, в системную таблицу маршрутизации.

Настройка демона zebra

Демон zebra выполняет роль прослойки между операционной системой и демонами динамической маршрутизации. Демоны динамической маршрутизации выступают в роли клиентов демона zebra и пользуются его API.

Первоначальная настройка демона zebra проста, нужно создать файл конфигурации /etc/quagga/zebra.conf с содержимым следующего вида:
hostname <hostname>
password <password>
enable password <secret_password>
После этого можно включать автозапуск демона при загрузке системы и запустить сам демон маршрутизации:
# systemctl enable zebra.service
# systemctl start zebra.service
Дальнейшее управление демоном, в том числе его конфигурирование, можно осуществлять при помощи команды telnet:
$ telnet 127.0.0.1 2601
Через telent доступен интерфейс командной строки, похожий на интерфейс командной строки устройств Cisco. После изменения настроек не забывайте сохранять их при помощи команды write.

Я настраивал демона на двух тестовых виртуальных машинах с именами bgp1 и bgp2.

Настройка демона bgpd

Демон bgpd реализует поддержку протокола динамической маршрутизации BGP.

Для первоначальной настройки демона bgpd нужно создать файл /etc/quagga/bgpd.conf.

На первой виртуальной машине содержимое файла конфигурации было примерно таким:
hostname bgp1
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.6
 network 169.254.253.0/24
 network 169.254.254.0/24
 neighbor 169.254.252.7 remote-as 64500
Эта виртуальная машина лишь анонсирует две сети на вторую виртуальную машину. Номера автономных систем на обеих виртуальных машинах одинаковы и взяты из диапазона 64496-64511 для примеров и документации. Поскольку используются одинаковые номера автономных систем, то в нашем случае будет использоваться вариант протокола iBGP - внутренний протокол пограничных шлюзов.

На второй виртуальной машине содержимое файла конфигурации было примерно следующим:
hostname bgp2
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.7
 neighbor 169.254.252.6 remote-as 64500
Эта виртуальная машина не анонсировала своих сетей, зато принимала анонсы от первой.

После первоначальной настройки можно на обеих виртуальных машинах включить автозапуск демонов bgpd при загрузке системы и запустить их:
# systemctl enable bgpd.service
# systemctl start bgpd.service
Подобно демону zebra, управление и настройку демона bgpd можно осуществлять при помощи команды telnet:
$ telnet 127.0.0.1 2605
Точно так же через telent доступен интерфейс командной строки, похожий на интерфейс командной строки устройств Cisco. После изменения настроек не забывайте сохранять их при помощи команды write.

Белый список маршрутов

Рассмотрим конфигурирование демона bgpd через telnet. Для примера настроим фильтрацию маршрутов на второй виртуальной машине. Для начала подключаемся через telnet:
$ telnet 127.0.0.1 2605
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

Hello, this is Quagga (version 1.1.1).
Copyright 1996-2005 Kunihiro Ishiguro, et al.


User Access Verification

Password:
Вводим пароль, который был указан в опции password.

Далее повышаем собственные привилегии при помощи команды enable. В ответ на запрос команды enable вводим пароль, который был указан в опции enable password.
bgp2> enable
Password: 
bgp2#
Далее переходим в режим настройки при помощи команды configure terminal:
bgp2# configure terminal 
bgp2(config)#
Настроим сначала список префиксов, который назовём PREFIX-LIST-FROM-BGP1:
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24
bgp2(config)#
Теперь создадим на основе этого списка префиксов маршрутную карту с именем MAP-FROM-BGP1:
bgp2(config) # route-map MAP-FROM-BGP1 permit 10
bgp2(config-route-map)#
Добавим в маршрутную карту список префиксов PREFIX-LIST-FROM-BGP1:
bgp2(config-route-map)# match ip address prefix-list PREFIX-LIST-FROM-BGP1
bgp2(config-route-map)#
И покинем диалог настройки маршрутной карты:
bgp2(config-route-map)# exit
bgp2(config)#
Осталось немного. Переходим в режим настройки протокола динамической маршрутизации bgp:
bgp2(config)# router bgp 64500
bgp2(config-router)#
Добавим созданную маршрутную карту для фильтрации маршрутов, получаемых от соседа:
bgp2(config-router)# neighbor 169.254.252.6 route-map MAP-FROM-BGP1 in
bgp2(config-router)#
Покидаем режим настройки bgp:
bgp2(config-router)# exit
bgp2(config)#
Сохраняем изменения конфигурации на диск:
bgp2(config)# write
Configuration saved to /etc/quagga/bgpd.conf
bgp2(config)#
Осталось выйти из режима конфигурирования:
bgp2(config)# exit
bgp2#
В процессе настройки из любого режима можно проверять правильность каждой введённой команды, просматривая текущий файл конфигурации при помощи команды show running-config:
bgp2(config)# show running-config                          

Current configuration:
!
hostname bgp2
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.7
 neighbor 169.254.252.6 remote-as 64500
 neighbor 169.254.252.6 route-map MAP-FROM-BGP1 in
!
 address-family ipv6
 exit-address-family
 exit
!
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1
!
line vty
!
end
Чтобы посмотреть текущую таблицу маршрутов, можно воспользоваться командой show ip bgp:
bgp2# show ip bgp 
BGP table version is 0, local router ID is 169.254.252.7
Status codes: s suppressed, d damped, h history, * valid, > best, = multipath,
              i internal, r RIB-failure, S Stale, R Removed
Origin codes: i - IGP, e - EGP, ? - incomplete

   Network          Next Hop            Metric LocPrf Weight Path
*>i169.254.253.0/24 169.254.252.6            0     100       0 i
*>i169.254.254.0/24 169.254.252.6            0     100       0 i

Displayed  2 out of 2 total prefixes
Если нужно применить новый или изменённый фильтр к уже принятым маршрутам, можно воспользоваться командой clear ip bgp такого вида:
bgp2# clear ip bgp 169.254.252.6 soft in
bgp2#
После этого все маршруты, не разрешённые маршрутной картой явным образом, должны пропасть из списка текущих маршрутов.

Чёрный список маршрутов

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

Не буду повторять предыдущий раздел, а расскажу кратко. Поскольку логика фильтрации обратная - нужно отбрасывать маршруты из списка, а не принимать их, то в маршрутной карте действие permit заменяется на deny:
bgp2(config)# route-map MAP-FROM-BGP1 deny 10
bgp2(config-route-map)# match ip address prefix-list PREFIX-LIST-FROM-BGP1
bgp2(config-route-map)# exit
bgp2(config)#
В списке префиксов действия permit не меняется. Чтобы отбросить анонсы сети 169.254.254.0/24, нужно поместить в список одну запись:
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
bgp2(config)#
Также может потребоваться не принимать анонсы маршрутов не только с точно совпадающим префиксом, но и анонсы маршрутов ко всем сетям, находящимся внутри указанной. Сделать это можно следующим образом:
bgp2(config)# ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24 le 32
bgp2(config)#
Выражение "le 32" означает, что условию удовлетворяют не только сети с префиксом 169.254.253.0/24, но и со всеми префиксами с длиной меньше 32 или равными 32. Например, если маршрутизатор BGP1 будет анонсировать маршруты к сетям 169.254.253.0/25 и 169.254.253.128/26, то оба анонса будут отброшены маршрутной картой.

Фильтрация исходящих анонсов

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

Делается это аналогично фильтрации принимаемых маршрутов, с той лишь разницей, что ключевое слово in нужно заменить на out и прописать имя соответствующей маршрутной карты:
bgp2(config)# router bgp 64500
bgp2(config-router)# neighbor 169.254.252.6 route-map MAP-TO-BGP1 out
bgp2(config-router)# exit
bgp2(config)#
Если маршрутизатор должен только принимать анонсы, то такая маршрутная карта будет выглядеть следующим образом:
bgp2(config)# route-map MAP-TO-BGP1 deny 10
bgp2(config-router)# exit
bgp2(config)#

Применение политик без разрыва сеансов BGP

При изменении маршрутных карт и списков префиксов, чтобы новые политики фильтрации вступали в силу, нужно выполнять команду следующего вида:
bgp2# clear ip bgp 169.254.252.6 soft in
bgp2#
Эта команда разрывает сеанс BGP с соседним маршрутизатором и устанавливает его заново. При этом соседний маршрутизатор вновь анонсирует весь список маршрутов, который и подвергается фильтрации.

Чтобы не разрывать сеансы BGP только лишь для того, чтобы применить новые политики фильтрации маршрутов, сетевые администраторы рекомендуют включить для соседа режим мягкой переконфигурации:
bgp2(config)# router bgp 64500
bgp2(config-router)# neighbor 169.254.252.6 soft-reconfiguration inbound
bgp2(config-router)# exit
bgp2(config)#
В этом режиме сеанс BGP с соседним маршрутизатором не разрывается, а фильтрация применяется к уже принятому ранее списку маршрутов.

Описание соседей

Даже если в конфигурации BGP описан только один соседний маршрутизатор, лучше снабдить его описанием, чтобы в дальнейшем не приходилось ориентироваться только на IP-адреса и номера автономных систем. Сделать это можно следующим образом:
bgp2(config)# router bgp 64500
bgp2(config-router)# neighbor 169.254.252.6 description bgp1
bgp2(config-router)# exit
bgp2(config)#

Группировка настроек соседей

Рассмотрим случай, когда необходимо настроить соседство с двумя маршрутизаторами, настройки которых в целом должны быть идентичны. В примере ниже маршрутизатор с именем bgp3 и IP-адресом 169.254.252.10 соседствует с маршрутизаторами bgp1 и bgp2, имеющими IP-адреса 169.254.252.5 и 169.254.252.6 соответственно:
hostname bgp3
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.10
 neighbor 169.254.252.5 remote-as 64500
 neighbor 169.254.252.5 route-map PREFIX-LIST-FROM-BGP1 in
 neighbor 169.254.252.5 soft-reconfiguration inbound
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 remote-as 64500
 neighbor 169.254.252.6 route-map PREFIX-LIST-FROM-BGP2 in
 neighbor 169.254.252.6 soft-reconfiguration inbound
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1 seq 2 permit 169.254.253.0/24
!
ip prefix-list PREFIX-LIST-FROM-BGP2 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP2 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1
!
route-map MAP-FROM-BGP2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP2
Настройки соседей по замыслу должны быть идентичными, но из приведённой выше конфигурации это не очевидно. Если конфигурацию будет читать другой системный администратор, то ему во-первых придётся прочитать довольно много, во-вторых первоначальный замысел может оказаться для него не очевидным. Он может изменить настройки одного из соседей, но забыть поправить подобным образом настройки второго соседа.

Первое, что приходит в голову - это объединить списки префиксов и маршрутные карты:
hostname bgp3
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.10
 neighbor 169.254.252.5 remote-as 64500
 neighbor 169.254.252.5 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor 169.254.252.5 soft-reconfiguration inbound
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 remote-as 64500
 neighbor 169.254.252.6 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor 169.254.252.6 soft-reconfiguration inbound
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1_2
Конфигурация стала короче, замысел стал более понятным, но всё ещё есть возможность поменять настройки одного маршрутизатора, не меняя настройки второго. Настройки соседей можно сгруппировать:
hostname bgp3
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.252.10
 neighbor NEIGHBORS-BGP1_2 peer-group
 neighbor NEIGHBORS-BGP1_2 remote-as 64500
 neighbor NEIGHBORS-BGP1_2 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor NEIGHBORS-BGP1_2 soft-reconfiguration inbound
 neighbor 169.254.252.5 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 1 permit 169.254.254.0/24
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 2 permit 169.254.253.0/24
!
route-map MAP-FROM-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1_2
Теперь оба соседа состоят в одной группе и их настройки объединены, так что будут меняться только одновременно, если специально не отделить их друг от друга. Получившийся файл конфигурации стал короче и нагляднее.

Ограничение доступа к терминалу

Т.к. для настройки демонов zebra и bpgd используется протокол telnet, то я бы не стал делать его доступным через внешние интерфейсы сервера. Трафик в telnet не шифруется, а если кому-то нужно поправить настройки, то пусть лучше зайдёт сначала на сервер по SSH, а потом уже подключается к терминалу zebra или quagga локально. Если бы мне пришла в голову мысль сделать терминал доступным на внешних интерфейсах, то я открыл доступ в пакетном фильтре только для необходимых IP-адресов. Тем не менее, параноики исходят из справедливости принципа Мерфи, поэтому стремятся защититься даже от тех ситуаций, которые кажутся почти невероятными. Мне порекомендовали защитить доступ к терминалу управления списками управления доступом, вот так:
access-list TERMINAL permit 127.0.0.1/32
access-list TERMINAL deny any
!
line vty
 access-class TERMINAL

Анонсирование адреса Loopback-интерфейса

Если BGP настраивается на сервере для того, чтобы сервер был доступен через два маршрутизатора, то IP-адрес сервера, анонсируемый соседям по протоколу BGP, настраивается не на физическом интерфейсе, а на так называемом Loopback-интерфейсе.

В Debian я не нашёл способа создать отдельный петлевой интерфейс, поэтому анонсируемый IP-адрес можно добавить на один-единственный петлевой интерфейс lo в качестве дополнительного IP-адреса. Сделать это можно при помощи команды следующего вида:
# ip addr add 169.254.251.1/32 dev lo
Чтобы дополнительный IP-адрес назначался интерфейсу lo при перезагрузке сервера, нужно в файле /etc/network/interfaces прописать аналогичные настройки:
auto lo:1
iface lo:1 inet static
  address 169.254.251.1
  netmask 255.255.255.255
После этого нужно:
  1. настроить демона, который реализует необходимый сервис, так чтобы он принимал входящие подключения только на этот IP-адрес,
  2. разрешить соответствующий трафик в пакетном фильтре,
  3. добавить IP-адрес в конфигурацию демона bgpd.
При выполнении последнего пункта стоит учесть правила хорошего тона и создать отдельную маршрутную карту на исходящие анонсы, которая будет разрешать демону bgpd анонсировать соседям только этот IP-адрес. Например, если сервер должен принимать от маршрутизаторов только маршруты по умолчанию, а анонсировать в их сторону только Loopback-адрес 169.254.251.1, то конфигурация может выглядеть следующим образом:
hostname server
password <password>
enable password <secret_password>
!
router bgp 64500
 bgp router-id 169.254.251.1
 network 169.254.251.1/32
 neighbor NEIGHBORS-BGP1_2 peer-group
 neighbor NEIGHBORS-BGP1_2 remote-as 64500
 neighbor NEIGHBORS-BGP1_2 route-map PREFIX-LIST-FROM-BGP1_2 in
 neighbor NEIGHBORS-BGP1_2 route-map PREFIX-LIST-TO-BGP1_2 in
 neighbor NEIGHBORS-BGP1_2 soft-reconfiguration inbound
 neighbor 169.254.252.5 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.5 description bgp1.stupin.su
 neighbor 169.254.252.6 peer-group NEIGHBORS-BGP1_2
 neighbor 169.254.252.6 description bgp2.stupin.su
!
ip prefix-list PREFIX-LIST-FROM-BGP1_2 seq 1 permit 0.0.0.0/0
ip prefix-list PREFIX-LIST-TO-BGP1_2 seq 1 permit 169.254.251.1/32
!
route-map MAP-FROM-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-FROM-BGP1_2
!
route-map MAP-TO-BGP1_2 permit 10
 match ip address prefix-list PREFIX-LIST-TO-BGP1_2
На случай, если демон bgpd аварийно завершится, можно добавить в демоне zebra статический маршрут по умолчанию с низким приоритетом через одного из соседей:
ip route 0.0.0.0/0 169.254.252.5 250

Использованные материалы

воскресенье, 15 сентября 2019 г.

Управление конфигурациями с помощью Asnible

Настройка управляющей машины

На управляющей машине устанавливаем ansible:
# apt-get install ansible
В домашнем каталоге создаём каталог, в котором будем хранить все файлы для настройки удалённых компьютеров. Если нужно работать над проектом совместно с другими системными администраторами и отслеживать изменения, сделанные ими, то лучше сразу же создать в этом каталоге git-репозиторий:
$ mkdir playbooks
$ git init
Создадим файл конфигурации ansible.cfg и пропишем в него следующие настройки:
[defaults]
inventory = hosts
remote_user = ansible
private_key_file = .ssh/id_rsa
Сразу же сгенерируем ssh-ключ, который будем устанавливать на удалённые системы для их настройки при помощи ansible:
$ mkdir .ssh
$ ssh-keygen -f .ssh/id_rsa
$ chmod go= -R .ssh
Каталог .ssh помещать в git-репозиторий не стоит.

Создадим файл hosts, в котором будем отмечать узлы, управляемые при помощи ansible. Например, файл с одним-единственным узлом с именем mon может выглядеть следующим образом:
mon ansible_host=169.254.252.2

Настройка управляемой машины

На управляемой машине после настройки сети и установки OpenSSH-сервера необходимо установить несколько пакетов, которые будут использоваться ansible:
# apt-get install python2.7-minimal python2.7-stdlib python-minimal
Также установим sudo, чтобы при удалённой настройке можно было выполнять действия от имени пользователя root:
# apt-get install sudo
Добавим учётную запись специально для удалённой настройки:
# useradd -c 'Ansible account' -m ansible
Сразу же дадим возможность этой учётной записи использовать sudo для работы с правами пользователя root:
# visudo
Добавляем строчки:
Defaults:ansible !requiretty
ansible ALL=(root:ALL) NOPASSWD:ALL
Скопируем на удалённую машину SSH-ключ:
$ ssh-copy-id -i .ssh/id_rsa.pub ansible@169.254.252.2

Первый сценарий

Для первого сценария возьмём простейшую задачу - настройки файла /etc/resolv.conf на удалённом компьютере.

Создадим на управляющей машине в каталоге проекта файл сценария resolvconf.yml со следующим содержимым:
---
- name: Configure resolv.conf
  hosts: mon
  become: True
  tasks:
    - name: copy /etc/resolv.conf
      template:
        src: templates/resolv.conf
        dest: /etc/resolv.conf
        owner: root
        group: root
        mode: 0644
Создадим каталог templates для шаблонов файлов конфигурации:
$ mkdir templates
Создадим шаблон конфигурации templates/resolv.conf для файла /etc/resolv.conf управляемого компьютера:
{% if resolvconf.domain %}
domain {{ resolvconf.domain }}
{% endif %}
{% for ip in resolvconf.nameservers %}
nameserver {{ ip }}
{% endfor %}
Значения переменных хранятся в так называемом реестре ansible. Он представляет собой файл в формате YML и может находиться либо в каталоге host_vars, либо в каталоге group_vars. В каталоге host_vars хранятся настройки, специфичные для каждого отдельного управляемого узла. Внутри этого каталога создаётся фай, имя которого совпадает с указанным в файле hosts. В каталоге group_vars хранятся настройки, общие для определённой группы узлов. Внутри этого каталога создаётся файл, имя которого совпадает с именем группы, указанным в файле hosts.

Создадим каталог host_vars с переменными узлов:
$ mkdir host_vars
И внутри каталога создадим файл mon со следующим содержимым:
resolvconf:
  domain: stupin.su
  nameservers:
    - 169.254.252.1
Осталось выполнить наш первый простейший сценарий. Для этого воспользуемся командой ansible-playbook:
$ ansible-playbook resolvconf.yml

Переключение на непривилегированного пользователя

В прошлом сценарии имелась опция, которая заставляла выполнять действия от имени пользователя root:
become: True
В случае, если нужно выполнять действия от имени другого, непривилегированного пользователя, можно столкнуться с проблемами. Например, администратором баз данных PostgresSQL является системный пользователь postgres. Создать нового пользователя или базу данных можно только от его имени. Поэтому, задача такого вида:
- name: createuser -P <login>
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases
Завершится такой ошибкой:
unable to connect to database: ВАЖНО:  пользователь "postgres" не прошёл проверку подлинности (Peer)
Однако и простого указания опций переключения на пользователя postgres оказывается недостаточно:
- name: createuser -P <login>
  become: True
  become_user: postgres
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases
В этом случае попытка выполнить задачу завершается следующей ошибкой (отформатировано для наглядности):
Failed to set permissions on the temporary files
Ansible needs to create when becoming an unprivileged user (rc: 1, err:
  chown: изменение владельца '/tmp/ansible-tmp-1549203485.91-25063771984034/': Операция не позволена
  chown: изменение владельца '/tmp/ansible-tmp-1549203485.91-25063771984034/postgresql_user.py': Операция не позволена
).
For information on working around this, see https://docs.ansible.com/ansible/become.html#becoming-an-unprivileged-user
После доработки в соответствии с материалами по ссылке, указанной в тексте ошибки, задача приобретает следующий вид:
- name: createuser -P <login>
  become: yes
  become_user: postgres
  vars:
    ansible_ssh_pipelining: true
  postgresql_user:
    name: "{{ item.login }}"
    password: "{{ item.password }}"
  with_items: "{{ postgresql_databases }}"
  when: postgresql_databases
Но и этого оказывается недостаточно, т.к. возникает следующая ошибка:
failed: [mon] (item={u'login': u'zabbix', u'password': u'xxxxxxxxxxxxxxxx', u'name': u'zabbix'}) => {
    "failed": true, 
    "item": {
        "login": "zabbix", 
        "name": "zabbix", 
        "password": "xxxxxxxxxxxxxxxx"
    }, 
    "module_stderr": "sudo: a password is required\n", 
    "module_stdout": ""
}

MSG:

MODULE FAILURE
Как видно, ansible пытается переключиться на указанного пользователя при помощи sudo. Но, когда я настраивал sudo для ansible, то указал разрешение переключаться без указания пароля только на пользователя root:
ansible ALL=(root:ALL) NOPASSWD:ALL
Чтобы разрешить пользователю ansible переключаться ещё и на пользователя postgres, нужно добавить через команду visudo ещё одно разрешение:
ansible ALL=(postgres:ALL) NOPASSWD:ALL
После этого задача выполняется успешно.

Немедленный перезапуск сервиса при изменениях

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

Именно так получилось у меня при подготовке роли, настраивающей сервис PostgreSQL. После изменения номера порта может потребоваться создать новые базы данных или пользователей, но пока сервис не был перезапущен, подключиться к нему по новому порту не получится, а старый может быть не известен. В таком случае можно запомнить результаты выполненных действий в переменных при помощи выражения register:
- name: vim /etc/postgresql/9.6/main/pg_hba.conf
  template:
    src: etc/postgresql/9.6/main/pg_hba.conf
    dest: /etc/postgresql/9.6/main/pg_hba.conf
    owner: postgres
    group: postgres
    mode: 0640
  register: pg_hba 

- name: vim /etc/postgresql/9.6/main/postgresql.conf
  template:
    src: etc/postgresql/9.6/main/postgresql.conf
    dest: /etc/postgresql/9.6/main/postgresql.conf
    owner: postgres
    group: postgres
    mode: 0644
  register: postgresql
И затем проверить результаты этих действий и немедленно перезапустить сервис, если его конфигурация была изменена:
- name: systemctl restart postgresql
  service:
    name: postgresql
    state: restarted
  when: (pg_hba.changed or postgresql.changed)

Использованные материалы

P.S.

Для более-менее полноценного использования Ansible рекомендую изучить роли. Роль представляет собой совокупность сценария, настроек по умолчанию и используемых файлов и шаблонов. В сценарии можно будет указать список ролей, которые нужно применить к узлам, указанным в сценарии. Благодаря ролям становится возможным собирать сценарии из крупных строительных блоков - ролей.

воскресенье, 8 сентября 2019 г.

Настройка текстового терминала для использования в virsh

Ранее я описывал установку и настройку virt-manager - графической программы для администрирования виртуальных машин, работающих под управлением KVM, Xen и других гипервизоров. Для управления начинкой виртуальных машин, как правило, достаточно SSH. Однако, виртуальная машина может оказаться не доступной по SSH из-за неполадок в работе сети или ошибок в сетевых настройках самой виртуальной машины. В таком случае можно подключиться к консоли виртуальной машины, однако и virt-manager может оказаться не доступным. Вместо virt-manger можно попробовать воспользоваться virsh, но как попасть в текстовую консоль виртуальной машины, если она не была настроена заранее?

Чтобы подстелить соломки, можно заранее настроить текстовую консоль в каждой из виртуальных машин. В случае недоступности виртуальной машины по сети или отсутствии virt-manager на ближайшем компьютере достаточно будет подключиться по SSH к компьютеру, на котором работает гипервизор, на нём запустить virsh, а оттуда уже попасть в консоль виртуальной машины. Займёмся настройкой.

Настройка виртуальной машины

Запускаем от пользователя root команду virsh:
# virsh
В запущенной оболочке virsh проверяем, есть ли у виртуальной машины текстовый терминал (вместо <vm> нужно указать имя виртуальной машины):
ttyconsole <vm>
Если команда ничего не вывела, значит виртуального терминала нет и его нужно настроить. Для этого отредактируем конфигурацию виртуальной машины, выполнив в оболочке virsh команду:
edit <vm>
Если в файле есть секция console, имеющая такой вид:
<console type='pty'>
  <target type='virtio' port='0'/>
</console>
То её нужно привести её к следующему виду:
<serial type='pty'>
  <target port='0'/>
</serial>
<console type='pty'>
  <target type='serial' port='0'/>
</console>
Если же секцию console найти не удалось, то нужно вставить указанный фрагмент вовнутрь секции devices.

Дальше нужно остановить виртуальную машину и запустить её снова. Для этого в оболочке virsh можно выполнить следующие две команды:
shutdown <vm>
start <vm>
После пересоздания виртуальной машины, в ней появится последовательное устройство.

Настройка демона терминала getty

Когда виртуальная машина запустится, нужно зайти в неё через графическую консоль (или через SSH) и выполнить две команды, которые включат терминал на виртуальном последовательном порту:
# systemctl enable serial-getty@ttyS0.service
# systemctl start serial-getty@ttyS0.service
После этого появится возможность подключиться к этому терминалу через virsh, при помощи команды следующего вида:
console <vm>
Однако, этим терминалом можно будет пользоваться только после загрузки системы. Нельзя будет увидеть ни меню загрузчика, ни сообщения, выводимые ядром операционной системы в процессе его загрузки. Чтобы терминалом можно было воспользоваться до загрузки операционной системы, нужно настроить консоль в загрузчике и в ядре.

Настройка консоли в GRUB 2 и Linux

Поскольку загрузчик передаёт опции ядру, то для настройки консоли в загрузчике и в ядре Linux понадобится отредактировать только конфигурацию загрузчика.

Для настройки консоли в загрузчике, нужно в файле /etc/default/grub добавить или заменить следующие опции:
GRUB_TERMINAL="serial console"
GRUB_SERIAL_COMMAND="serial --unit=0 --speed=115200 --word=8 --parity=no --stop=1"
Мне потребовалось поискать в интернете примеры настройки, при которых консоль на последовательном порту не вызывала бы отключение графической консоли. Указанный выше вариант позволяет пользоваться меню GRUB и через последовательную консоль, и через графическую.

Для настройки консоли в ядре Linux нужно в том же файле /etc/default/grub найти опцию GRUB_CMDLINE_LINUX и добавить в неё следующие опции ядра:
console=tty0 console=ttyS0,115200n8
В моём случае никаких опций ядру не передавалось, поэтому строчка приняла следующий вид:
GRUB_CMDLINE_LINUX="console=tty0 console=ttyS0,115200n8"

Теперь нужно применить изменения в конфигурации GRUB при помощи следующей команды:
# update-grub

Результат

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



Ниже показан процесс загрузки системы на обеих консолях:

Использованные материалы

воскресенье, 1 сентября 2019 г.

Virt-manager для управления виртуальными машинами под управлением KVM и Xen

Для управления виртуальными машинам, работающими под управлением систем виртуализации KVM и Xen можно использовать программу с графическим интерфейсом, которая называется Virt-manager. Программа использует демон libvirt, установленный на компьютере с системой виртуализации. Для подключения Virt-manager к демону libvirt можно использовать SSH, что довольно удобно и безопасно. Как правило, демон SSH бывает уже установлен на компьютерах, доступных по сети, поэтому не нужно заботиться о настройке отдельного сетевого демона. Плюс к тому, SSH поддерживает аутентификацию пользователей и надёжное шифрование передаваемых данных.

Настройка демона libvirt для управления виртуальными машинами под управлением KVM (на примере Debian Stretch)

Проверим наличие аппаратной поддержки виртуализации:
$ egrep -c '(vmx|svm)' /proc/cpuinfo
Устанавливаем систему виртуализации qemu с поддержкой KVM и демон libvirt для управления виртуальными машинами, работающими под управлением KVM:
# apt-get install qemu-kvm libvirt-daemon-system
Добавляем пользователя, от имени которого будем заходить на сервер по SSH, в группу libvirt, чтобы он имел доступ к управлению виртуальными машинами:
# usermod -aG libvirt stupin
Пользователь получит доступ к Unix-сокету /var/run/libvirt/libvirt-sock

Установим netcat из OpenBSD, поддерживающий опцию -U для подключения к Unix-сокету. Это нужно для перенаправления подключения от SSH-клиента в Unix-сокет libvirt:
# apt-get install netcat-openbsd
Осталось включить и запустить демона:
# systemctl enable libvirtd
# systemctl start libvirtd

Настройка демона libvirt для управления виртуальными машинами под управлением Xen (на примере Debian Wheezy)

В моём случае Xen был развёрнут в операционной системе Debian Wheezy, где пакет с демоном libvirt называется libvirt-bin. Установим его:
# apt-get install libvirt-bin
Теперь, чтобы пользователь, от имени которого мы будем подключаться по SSH к серверу виртуализации, смог подключиться к демону libvirtd, нужно добавить его в группу libvirt:
# usermod -aG libvirt stupin
Пользователь получит доступ к Unix-сокету /var/run/libvirt/libvirt-sock

Для того, чтобы демон libvirt смог работать с системой виртуализации Xen, нужно включить HTTP-сервер, встроенный в xend. Демон libvirt будет выполнять роль посредника, транслируя входящие запросы в запросы к xend.

Настроим HTTP-сервер, ожидающий подключений на порту 8000 и адресе 127.0.0.1. Для этого нужно прописать в файл /etc/xen/xend-config.sxp следующие опции:
(xend-http-server yes)
(xend-address localhost)
(xend-port 8000)
Чтобы настройки xend вступили в силу, нужно перезапустить его (пример для Debian Wheezy):
# /etc/init.d/xen restart
Будьте осторожны - в конфигурации по умолчанию при перезапуске демона xend перезапускаются также и все виртуальные машины. Чтобы избежать этого, нужно при создании виртуальной машины прописать в её конфигурацию такие настройки:
on_xend_start = 'ignore'
on_xend_stop = 'ignore'
У меня эти настройки были прописаны в каждой виртуальной машине, поэтому я перезапускал xend без опасений.

Установка и использование Virt-manager

Устанавливаем на компьютере администратора пакет с GUI-интерфейсом для управления виртуальными машинами:
# apt-get install virt-manager
Теперь можно попробовать сделать то, ради чего всё и затевалось: подключиться к системе виртуализации из программы virt-manager. Ниже приведены снимки экрана, иллюстрирующие этот процесс.

Запуск virt-manager:

Добавление нового подключения:

Новое подключение к системе виртуализации Xen:

Настройки удалённого подключения к системе виртуализации Xen через SSH:

Подключение к системе виртуализации установлено, виден список виртуальных машин:

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

Типичные проблемы

Не установлен пакет netcat-openbsd

Не удалось подключиться к libvirt.

Для взаимодействия с удалённым узлом необходимо, чтобы версия netcat/nc поддерживала параметр -U.

Не установлен пакет libvirt-bin или libvirt-daemon-system

Не удалось подключиться к libvirt.

End of file while reading data: nc: unix connect failed: No such file or directory: Ошибка ввода/вывода

Убедитесь, что на удалённом узле запущен libvirtd.

Не настроен HTTP-сервер xend

Не удалось подключиться к libvirt.

unable to connect to 'localhost:8000': В соединении отказано

Убедитесь, что на удалённом узле запущен libvirtd.

Не установлен пакет gir1.2-spice-client-gtk-3.0

Ошибка подключения к графической консоли:
Error opening Spice console, SpiceClientGtk missing

воскресенье, 25 августа 2019 г.

IPv6 от Дом.ру (ЭР-Телеком) в Debian Stretch

Отключение IPv6

До сих пор мне приходилось лишь отключать поддержку IPv6 на компьютерах, т.к. она скорее создавала дополнительные риски для безопасности системы, чем приносила какую-то практическую пользу. Напомню, что для отключения IPv6 на уровне операционной системы нужно выполнить команду:
# sysctl -w net.ipv6.conf.all.disable_ipv6=1
Чтобы поддержка IPv6 не включалась при перезагрузке системы, нужно прописать в файл /etc/sysctl.conf такую строчку:
net.ipv6.conf.all.disable_ipv6=1
При этом интерфейсам не будут присваиваться адреса IPv6, однако различные сетевые демоны по-прежнему будут готовы обслужить запрос, поступивший через стек IPv6. Чтобы полностью отключить весь стек IPv6, нужно в файле /etc/default/grub добавить в переменную GRUB_CMDLINE_LINUX опцию, которая сообщит ядру о необходимости отключить весь стек IPv6:
GRUB_CMDLINE_LINUX="ipv6.disable=1"
После этого нужно обновить файлы конфигурации загрузчика GRUB при помощи следующей команды:
# update-grub
И перезагрузить систему.

Однако, недавно в личном кабинете Дом.ru моё внимание привлёк переключатель поддержки IPv6. Нельзя сказать, что в IPv6 и сейчас есть много пользы, однако поддержка IPv6 со стороны провайдера побудила меня изучить вопрос. Я достал с полки когда-то купленную книжку IPv6. Администрирование сетей и стал почитывать вечерами.

Надо сказать, что принципы организации IPv6 значительно отличаются от принципов организации ныне используемого протокола IPv4. Это не просто новая версия IPv4 с увеличенной длинной поля IP-адреса. Разработчики протокола подошли к задаче основательно, пытаясь решить множество проблем, накопившихся в IPv4. На мой взгляд, IPv6 напоминает чем-то гибрид IPv4 и IPX.

Организация сетей IPv6 с точки зрения магистрального сетевого администратора

С точки зрения сетевого администратора, работающего в компании-провайдере, IPv6 в целом отличается от IPv4 довольно мало. Провайдер обращается к региональному регистратору для получения блока IPv6-адресов. Регистраторы выдают провайдерам блоки с префиксами /32 из адресного пространства 2000::/3 - это «белые» IPv6-адреса.

Следующие 32 бита - это номера сетей внутри блока. Провайдерам настоятельно рекомендуется анонсировать по протоколу BGP префиксы не длиннее /48, чтобы глобальные таблицы маршрутов не разрослись сверх меры. Клиентам рекомендуется выдавать префиксы сетей с маской /56. Это значит, что из 32 бит 24 бита остаются в распоряжении провайдера, а следующие 8 бит клиент может использовать по своему усмотрению. То есть провайдер может раздать по 256 сетей каждому из максимум 16 миллионов (2^24) клиентов, имея всего один блок /32. При этом у клиента остаётся возможность организовать адресное пространство таким образом, чтобы выделить каждому из филиалов по несколько сетей для различных нужд. Например, 16 филиалам по 16 сетей, или 64 филиалам по 4 сети.

Наконец, оставшиеся 64 бита используются в качестве идентификатора интерфейса сетевого устройства внутри одной сети. Одна сеть может содержать столько устройств, сколько необходимо. Для формирования идентификатора интерфейса используются идентификаторы EUI-64. Идентификатор EUI-64 получается из MAC-адреса следующим образом:
  1. у первого октета MAC-адреса выставляется в единицу бит 1 (выполняется операция «побитовое или» со значением 0x02),
  2. между первыми и последними тремя октетами вставляется два октета - ff fe.

Организация сетей IPv6 с точки зрения сетевого администратора

Для сетевого администратора есть несколько интересных отличий IPv6 от IPv4.

Во-первых, в IPv6 встроены функции автоконфигурации сетевых интерфейсов. Каждый интерфейс назначает себе IPv6-адрес с префиксом fe80::/10, который не подлежит маршрутизации. Эти автоконфигурируемые IPv6-адреса используются для общения с маршрутизаторами и соседями по сети. Благодаря этому минимальная связность сети обеспечивается автоматически, а протоколы конфигурирования значительно упрощаются.

Во-вторых, т.к. автоконфигурирование обеспечивает минимальную связность сети, упрощаются протоколы обнаружения соседей и более полного автоконфигурирования. В IPv6 функции ARP и частично DHCP включены в протокол ICMPv6. Вместо ARP в ICMPv6 встроен протокол обнаружения соседей NDP - Neighbor Discovery Protocol. Вместо DHCP для получения маршрутов используется функция ICMPv6 под названием RA - Router Advertisement. Для получения адресов DNS-серверов и доменного суффикса по умолчанию в ICMPv6 существуют аналогичные функции RDNSS - Recursive DNS Servers и DNSSL - DNS Search List. Для более сложных случаев можно использовать DHCPv6, роль которого в IPv6 значительно снизилась - он используется для получения узлами различных настроек, таких как списки DNS или NTP-серверов, но не используется для конфигурирования адресов интерфейсов или раздачи информации о маршрутах.

В-третьих, IPv6 не использует широковещательные рассылки, а усиленно использует мультикаст-адресацию. Например, в протоколе NDP, чтобы узнать MAC-адреса устройства в сети, запрос отправляется на мультикаст-адрес, сформированный из части IPv6-адреса искомого устройства. Т.к. коммутаторам необходимо помнить, какие из портов относятся к каждому мультикаст-адресу, увеличиваются требования к максимальному объёму таблицы коммутации, но при этом снижается паразитная нагрузка на каналы связи, коммутаторы и конечные узлы, возникавшая из-за необходимости рассылать широковещательные пакеты. В IPv6 отпадает всякий смысл в делении крупных сетей на мелкие широковещательные домены. Остаётся осмысленным только выделение сети по организационным соображениям или из соображений безопасности. Например, можно выделять по отдельной сети для филиалов компании, или по сети для VoIP-шлюзов в филиале, с целью ограничить доступ к сети VoIP-шлюзов на маршрутизаторах, вне зависимости от количества оборудования в этой сети. В одной сети, благодаря использованию мультикаст-рассылок, могут уживаться хоть миллионы устройств - лишь бы коммутаторам хватало объёма таблицы коммутации.

Теперь можно перейти к практике.

Настройка одного интерфейса с "белым" адресом IPv6

Перед тем, как настраивать IPv6, сначала нужно настроить PPPoE-подключение с использованием IPv4. Сделать это можно одной из моих прошлых статей: Настройка PPPoE-подключения к ЭР-Телеком/Дом.ru (Уфанет, Башинформсвязь).

Добавим в файл /etc/ppp/peers/ertelecom одну дополнительную опцию:
+ipv6
Можно попробовать переустановить PPPoE-подключение:
# ifdown ertelecom
# ifup ertelecom
После переустановки подключения на интерфейсе ppp0 можно будет увидеть IPv6-адрес. Посмотрим его при помощи такой команды:
# ip addr show ppp0
Однако, этот IPv6-адрес начинается с префикса fe80::/10, который аналогичен IPv4-адресам из сети 169.254.0.0/16. Это самоконфигурируемые IPv6-адреса, которые действуют только в пределах одного сегмента сети и не маршрутизируются в другие сегменты. В случае с Ethernet-интерфейсами самоконфигурируемый адрес назначается следующим образом:
  1. в качестве префикса выбирается префикс fe80::/10,
  2. в качестве идентификатора интерфейса используется идентификатор EUI-64, полученный из MAC-адреса интерфейса.
Идентификатор EUI-64 получается из MAC-адреса следующим образом:
  1. у первого октета MAC-адреса выставляется в единицу бит 1 (выполняется операция «побитовое или» со значением 0x02),
  2. между первыми и последними тремя октетами вставляется два октета - ff fe.
В случае с PPP-подключениями не существует MAC-адреса, из которого можно было бы сгенерировать младшие 64 бита адреса, поэтому стороны генерируют случайные идентификаторы и согласовывают их друг с другом: соглашаются на предложенные идентификаторы или отказываются, предлагая другие. Именно такие адреса и были настроены на интерфейсе ppp0.

Для получения «белого» префикса потребуется установить и настроить клиент DHCPv6, который запросит через интерфейс ppp0 префикс IPv6 и настроит его на Ethernet-интерфейсе. Я пробовал сначала воспользоваться демоном dhcpcd5, однако он не желал настраивать Ethernet-интерфейсы, для которых в файле /etc/network/interfaces не указан метод настройки dhcp:
Sep 11 22:39:19 stupin systemd[1]: Starting LSB: IPv4 DHCP client with IPv4LL support...
Sep 11 22:39:19 stupin dhcpcd[30762]: Not running dhcpcd because /etc/network/interfaces ... failed!
Sep 11 22:39:19 stupin dhcpcd[30762]: defines some interfaces that will use a DHCP client ... failed!
Sep 11 22:39:19 stupin systemd[1]: Started LSB: IPv4 DHCP client with IPv4LL support.
Я попытался включить настройку интерфейса по DHCPv6, вписав в файл /etc/network/interfaces следующие настройки:
iface eth0 inet6 dhcp
  request_prefix 1
Однако даже с такими настройками dhcpcd5 запускаться отказывался. Поэтому я решил попробовать другой DHCP-клиент, которым оказался клиент из проекта WIDE-DHCPv6.

Установим DHCPv6-клиент:
# apt-get install wide-dhcpv6-client
Пропишем настройки DHCPv6-клиента в файле /etc/wide-dhcpv6/dhcp6c.conf:
interface ppp0 {
  send ia-pd 0;
};

id-assoc pd 0 {
  prefix-interface eth0 {
    sla-len 0;
    ifid 1;
  };
};
Пропишем в файле /etc/default/wide-dhcpv6-client список интерфейсов, через которые DHCPv6-клиент должен запрашивать настройки:
INTERFACES="ppp0"
Включим автозапуск wide-dhcp6-client при загрузке системы и перезапустим его, чтобы новые настройки вступили в силу:
# systemctl enable wide-dhcpv6-client
# systemctl restart wide-dhcpv6-client
Теперь DHCPv6-клиент получает через интерфейс ppp0 префикс вида 2a02:2698:882X:XXXX::/64 и настраивает на интерфейсе eth0 «белый» IPv6-адрес с использованием этого префикса. Что характерно, выдаваемый IPv6-префикс - динамический, т.к. при каждом переподключении выдаётся другой.

В рецепте настройки DHCPv6-клиента написано, что для корректного удаления IPv6-адреса с Ethernet-интерфейса нужно соорудить дополнительные «костыли» в файлах /etc/ppp/ipv6-up.d/20-wide-client-start и /etc/ppp/ipv6-down.d/20-wide-client-stop. Я, однако, «костыли» немного преобразовал, чтобы при отключении PPPoE останавливался только тот экземпляр клиента DHCPv6, который получает настройки через подключение PPPoE.

Также при установке подключения нужно присвоить sysctl-переменной net.ipv6.conf.ppp0.accept_ra значение 2, чтобы система добавляла в таблицу маршрутизации IPv6-маршруты, анонсированные провайдером через интерфейс PPPoE. Присваивать это значение нужно именно через эти скрипты, т.к. интерфейс ppp0 появляется только после установки подключения и по умолчанию значение sysctl-переменной net.ipv6.conf.ppp0.accept_ra равно единице - система принимает анонсы маршрутов, но не добавляет их в таблицу маршрутизации.

В результате мои «костыли» приняли несколько другой вид. Содержимое файла /etc/ppp/ipv6-up.d/route:
#!/bin/sh

case "$PPP_IPPARAM" in
        ertelecom)

        sysctl -w net.ipv6.conf.$PPP_IFACE.accept_ra=2
        dhcp6ctl -C start interface $PPP_IFACE
        ;;

        *)
        echo "No PPP_IPPARAM defined"
        ;;
esac
Содержимое файла /etc/ppp/ipv6-down.d/route:
#!/bin/sh

case "$PPP_IPPARAM" in
        ertelecom)

        dhcp6ctl -C stop interface $PPP_IFACE
        ;;

        *)
        echo "No PPP_IPPARAM defined"
        ;;
esac
На обоих файлах нужно выставить бит исполнимости:
# chmod +x /etc/ppp/ipv6-up.d/route
# chmod +x /etc/ppp/ipv6-down.d/route

Настройка фильтрации пакетов IPv6

Хотя пока мало кто пользуется IPv6, всё же не стоит выставлять свой компьютер в «дикий» интернет, не прикрыв его пакетным фильтром. Базовая настройка пакетного фильтра такова:
# ip6tables -A INPUT -i lo -j ACCEPT
# ip6tables -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# ip6tables -A INPUT -p icmpv6 -m icmpv6 ! --icmpv6-type redirect -j ACCEPT
# ip6tables -A INPUT -i ppp0 -p udp -m udp -s fe80::/10 --sport 547 --dport 546  -j ACCEPT
# ip6tables -A INPUT DROP
В настроенных выше правилах пакетного фильтра разрешается:
  1. любой трафик через локальный петлевой интерфейсе lo,
  2. любой входящий ICMPv6-трафик, кроме переадресаций,
  3. разрешается принимать настройки по протоколу DHCPv6 через интерфейсе ppp0.
Сохраним настроенные правила фильтрации пакетов IPv6 в файл /etc/network/ip6tables:
# ip6tables-save > /etc/network/ip6tables
И пропишем их автоматическую загрузку в файл /etc/network/interfaces при поднятии локального петлевого интерфейса:
auto lo
iface lo inet loopback
  pre-up iptables-restore < /etc/network/iptables
  pre-up ip6tables-restore < /etc/network/ip6tables

Настройка BIND и openresolv для работы с IPv6

В одной из моих прошлых заметок описана Настройка кэширующего DNS-сервера BIND в связке с openresolv. Т.к. я продолжаю использовать эту связку, мне нужно приспособить её для работы с IPv6.

Первым делом нужно включить обслуживание запросов на IPv6-адресах в bind9. Для этого открываем файл /etc/bind/named.conf.options и вписываем опцию для прослушивания всех IPv6-адресов компьютера:
listen-on-v6 { any; };
Чтобы bind9 отвечал на запросы с IPv6-адреса ::1 - аналога IPv4-адреса 127.0.0.1, нужно добавить этот адрес в список адресов, которые могут выполнять рекурсивные запросы. В моём случае список адресов, которым разрешается выполнять рекурсивные запросы к DNS-серверу, выглядит следующим образом:
allow-recursion { 127.0.0.1; 169.254.253.0/24; 169.254.254.0/24; ::1; };
Осталось перезапустить bind9, чтобы он начал принимать запросы на IPv6-адресах:
# systemctl restart bind9
Теперь нужно настроить openresolv так, чтобы он прописывал в файл /etc/resolv.conf IPv6-адрес DNS-сервера - ::1. Для этого открываем на редактирование файл /etc/resolvconf.conf и приводим опцию name_servers к следующему виду:
name_servers="127.0.0.1 ::1"
Осталось обновить файл /etc/resolv.conf в соответствии с новыми настройками openresolv. Сделать это можно при помощи следующей команды:
# resolvconf -u

Проверка правильности настройки IPv6

Проверим правильность настройки IPv6, открыв веб-страницу для тестирования IPv6 - Проверьте ваше IPv6 подключение. Результат проверки в моём случае выглядел следующим образом:

Ещё один тест IPv6, более подробный и требовательный - IPv6 test - IPv6/4 connectivity and speed test. Результат этой проверки оказался следующим:

К сожалению, настроить PTR-запись для IPv6-адреса может только провайдер, но нельзя настроить PTR-запись для IPv6-адреса, префикс которого провайдер выдаёт каждый раз разный. Я задавал вопрос в техподдержку ЭР-Телекома о том, возможно ли выдавать один и тот же «статический» префикс IPv6 подобно тому, как это организовано в случае «статического» адреса IPv4. Мне ответили, что такой возможности в настоящее время у них нет. Поэтому в этом тесте мне удалось добиться максимально возможного количества баллов - 19 из 20. Для получения 20 баллов уже потребуются телодвижения со стороны провайдера.

Раздача "белых" IPv6-адресов устройствам в локальной сети

Провайдер выдал нам «белый» префикс IPv6 с маской /64, а это значит, что у нас имеется целая сеть, IPv6-адреса из которой мы можем раздавать своим устройствам. Даже самое крупное предприятие с сотнями тысяч компьютеров может уместиться в одной такой сети, выдав каждому сетевому узлу по одному адресу IPv6. Нет никакой необходимости использовать NAT, а задача защиты устройств от «дикого» интернета решается фильтрацией пакетов. У крупных предприятий может возникнуть потребность в получении от провайдера более коротких префиксов лишь при необходимости логически организовать свою сеть: выделить отдельные сети своим подразделениям (например, филиалам в разных городах), а в подразделениях - выделить отдельные подсети для разных целей (например, отдельная сеть для компьютеров сотрудников, отдельная - для голосовых шлюзов и т.д).

Я лично столкнулся с необходимостью иметь хотя бы две сети. Одну из них я хотел бы использовать на Ethernet-интерфейсе, а другую - на WiFi-интерфейсе. Вообще, RFC6177 рекомендует выдавать клиентам префиксы длиной от /48 до /56, но провайдер не следует рекомендациям, жадничает и выдаёт префиксы /64. Сначала я попробовал запросить у провайдера сразу два префикса, изменив файл конфигурации /etc/wide-dhcpv6-client/dhcp6c.conf следующим образом:
interface ppp0 {
  send ia-pd 0;
  send ia-pd 1;
};

id-assoc pd 0 {
  prefix-interface eth0 {
    sla-len 0;
    ifid 1;
  };
};

id-assoc pd 1 {
  prefix-interface wlan0 {
    sla-len 0;
    ifid 1;
  };
};
Но в ответ на такой трюк DHCPv6-сервер провайдера просто перестал отвечать на мои запросы. Пробовал воспользоваться NDP-прокси ndppd - аналогом ARP-прокси для IPv6 и демоном ndprbrd. Вместе они должны были перехватывать NDP-запросы, транслировать их на другой интерфейс, а при получении ответа добавлять маршрут к обнаруженному соседу через конкретный интерфейс. Но по не понятным мне причинам этот вариант тоже не взлетел. Остаётся только ждать и надеяться, что в ЭР-Телекоме когда-нибудь реализуют делегирование префиксов короче /64. Даже /60, позволяющей выделить 16 сетей /64 мне для дома хватило бы с головой.

Поскольку на Ethernet-интерфейсе у меня нет оборудования, имеющего полноценную поддержку IPv6, но есть нотубук с Debian, который может подключаться к WiFi-сети, то я решил повесить эту сеть на интерфейс wlan0.

Т.к. на моём компьютере установлен DNS-сервер, я решил в анонсах RA раздавать его адрес. Правда, поскольку выдаваемый провайдером IPv6-префикс - динамический, придётся воспользоваться локальными самосконфигурированными на интерфейсах IPv6-адресами. Первым делом разрешим DNS-серверу обслуживать запросы на локальных адресах. Для этого откроем в текстовом редакторе файл /etc/bind/named.conf.options и отредактируем список адресов, для которых будут обслуживаться рекурсивные запросы. В этот список я добавил сеть fe08::/10:
allow-recursion { 127.0.0.1; 169.254.253.0/24; 169.254.254.0/24; ::1; fe08::/10; };
Это не очень хорошо, т.к. провайдер тоже сможет через PPPoE-подключение обращаться с рекурсивными запросами к моему DNS-серверу. Педант в моей душе немного негодует, однако сумел договориться с практиком, который не нашёл других выходов из этой ситуации. Сообщим DNS-серверу об изменившихся настройках:
# systemctl reload bind9
Для того, чтобы раздавать IPv6-адреса из выделенной нам сети, установим и настроим демона radvd - Router ADVertisement Daemon - демона объявления маршрутизатора. Установим одноимённый пакет:
# apt-get install radvd
Создадим файл /etc/radvd.conf со следующим содержимым:
interface wlan0
{
  AdvSendAdvert on;
  MaxRtrAdvInterval 60;

  prefix ::/64
  {
    AdvValidLifetime 600;
    AdvPreferredLifetime 120;
  };

  RDNSS fe80::c24a:ff:fe9f:89fc {};
  DNSSL stupin.su {};
};
Включим и запустим демона:
# systemctl enable radvd
# systemctl start radvd
Теперь устройства в локальной сети могут запрашивать префикс у нашего компьютера. Если на компьютере настроена фильтрация пакетов, то для того, чтобы клиенты смогли обращаться к radvd, нужно разрешить входящие UDP-пакеты на порт 58:
# ip6tables -A INPUT -i eth0 -p udp -m udp --dport 58  -j ACCEPT
Если устройства в локальной сети должны иметь доступ к DNS-серверу, то нужна пара правил, полностью аналогичных таковым для IPv4:
# ip6tables -A INPUT -p udp -m udp --dport 53 -j ACCEPT
# ip6tables -A INPUT -p tcp -m tcp --dport 53 -j ACCEPT
Чтобы разрешить пропускать трафик из локальной сети во внешнюю сеть, нужно при помощи команды sysctl выставить несколько настроек:
# sysctl -w net.ipv6.conf.all.forwarding=1
# sysctl -w net.ipv6.conf.default.forwarding=1
Чтобы эти настройки применялись автоматически при перезагрузке компьютера, нужно вписать их в файл /etc/sysctl.conf:
net.ipv6.conf.all.forwarding=1
net.ipv6.conf.default.forwarding=1
Если на компьютере настроена фильтрация пакетов, можно разрешить движение трафика между локальным и внешним интерфейсами. Например, я разрешил только движение пакетов ICMPv6:
# ip6tables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# ip6tables -A FORWARD -p icmpv6 -m icmpv6 ! --icmpv6-type redirect -j ACCEPT
# ip6tables -P FORWARD DROP
Ноутбуку, который будет подключаться к сети, кое-что всё-таки разрешим:
# ip6tables -A FORWARD -i wlan0 -p tcp -m multiport --dports 21,22,25,43,80,110,143,443,587,993,995,5222,5223 -j ACCEPT
После изменения настроек фильтрации пакетов не забудьте записать их:
# ip6tables-save > /etc/network/ip6tables
Теперь настала очередь настройки системы на ноутбуке. Во-первых, поставим rdnssd для получения адресов DNS-серверов:
# apt-get install rdnssd
Включим и запустим его:
# systemctl enable rdnssd
# systemctl start rdnssd
Для правильной работы rndssd понадобится также пакет resolvconf. Если он ещё не установлен в системе, нужно его поставить:
# apt-get install resolvconf
Теперь пропишем в файле /etc/network/interfaces, как нужно настраивать IPv6 на интерфейсе wlan0:
iface wlan0 inet6 auto
  privext 2
Т.к. к префиксу, полученному через RA, добавляется идентификатор интерфейса, полученный из MAC-адреса компьютера, то MAC-адрес служит отличным идентификатором, по которому можно отслеживать интернет-активность конкретного устройства. Этот идентификатор остаётся постоянным даже при смене провайдера. Опция privext включает генерацию случайного идентификатора интерфейса, а также приоритетное использование IPv6-адреса, полученного из этого идентификатора. Таким крупным компаниям как Google это нисколько не помешает отслеживать активность устройства, но и облегчать задачу всем подряд тоже не стоит.

Осталось отключиться от WiFi-сети и снова подключиться к ней и проверить доступность IPv6-ресурсов с ноутбука:
# ifdown wlan0
# ifup wlan0
Заходим на указанные выше ресурсы для тестирования IPv6 и любуемся результатами. В этом случае по второй ссылке удалось набрать лишь 17 баллов, т.к. ICMPv6-запросы были слишком медленными и не успевали отрабатывать за отведённое время.

Использованные материалы

IPv6 от Дом.ru (ЭР-Телеком) на сервере Ubuntu/Debian

воскресенье, 18 августа 2019 г.

Сборка плагинов и тем Wordpress в deb-пакеты

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

Например, плагины и темы на официальном сайте wordpress располагаются по ссылкам вполне предсказуемого формата. Благодаря этому, возможно обобщить скрипт сборки deb-пакетов с плагинами и темами для Wordpress. Получившийся скрипт оказывается способен собирать deb-пакеты с любыми плагинами и темами, имеющимися на официальном сайте:
#!/bin/sh

build()
{
TYPE="$1"
NAME="$2"
VERSION="$3"
DEPENDS="$4" || ""

if [ -n "$DEPENDS" ]
then
  DEPENDS=", $DEPENDS"
fi

case "$TYPE" in
  plugin)
    PACKAGE="wordpress-$NAME"
    ;;

  theme)
    PACKAGE="wordpress-theme-$NAME"
    ;;

  *)
    echo "$TYPE $NAME $VERSION: Unknown type"
    return 2
    ;;
esac

DIR="${PACKAGE}_${VERSION}"

wget --quiet https://downloads.wordpress.org/$TYPE/$NAME.$VERSION.zip 2>/dev/null \
  || wget --quiet https://downloads.wordpress.org/$TYPE/$NAME.zip 2>/dev/null \
  || (echo "$TYPE $NAME $VERSION: Failed to download ZIP-file" >/dev/stderr && return 1)

unzip -q $NAME.$VERSION.zip 2>/dev/null \
  || unzip -q $NAME.zip 2>/dev/null \
  || (echo "$TYPE $NAME $VERSION: Failed to unpack ZIP-file" > /dev/stderr && return 1)

rm $NAME.$VERSION.zip 2>/dev/null || rm $NAME.zip

mkdir -p $DIR/DEBIAN

# Формируем описание пакета
cat - > $DIR/DEBIAN/control <<END
Package: $PACKAGE
Version: $VERSION
Architecture: all
Depends: wordpress
Maintainer: Vladimir Stupin <vladimir@stupin.su>
Description: $NAME $TYPE for Wordpress
 $NAME $TYPE for Wordpress
 Homepage: https://wordpress.org/${TYPE}s/$NAME/
END

chmod 755 $DIR/DEBIAN/control

cat - > $DIR/DEBIAN/postinst <<END
#!/bin/sh
# postinst script for Wordpress $TYPE $NAME

set -e

# summary of how this script can be called:
#        * <postinst> \`configure' <most-recently-configured-version>
#        * <old-postinst> \`abort-upgrade' <new version>
#        * <conflictor's-postinst> \`abort-remove' \`in-favour' <package>
#          <new-version>
#        * <deconfigured's-postinst> \`abort-deconfigure' \`in-favour'
#          <failed-install-package> <version> \`removing'
#          <conflicting-package> <version>
# for details, see http://www.debian.org/doc/debian-policy/ or
# the debian-policy package

case "\$1" in
  configure)
        # Change permissions to application files
        if ! dpkg-statoverride --list /usr/share/wordpress/wp-content/${TYPE}s/$NAME >/dev/null; then
                chown -R root:root /usr/share/wordpress/wp-content/${TYPE}s/$NAME
                chmod 755 /usr/share/wordpress/wp-content/${TYPE}s/$NAME
                find /usr/share/wordpress/wp-content/${TYPE}s/$NAME -type d | xargs chmod 755
                find /usr/share/wordpress/wp-content/${TYPE}s/$NAME -type f | xargs chmod 644
        fi
  ;;
  abort-upgrade|abort-remove|abort-deconfigure)
  ;;
  *)
    echo "postinst called with unknown argument \\\`\$1'" >&2
    exit 1
  ;;
esac
END

chmod 0755 $DIR/DEBIAN/postinst

# Формируем содержимое пакета
mkdir -p $DIR/usr/share/wordpress/wp-content/${TYPE}s/
cp -R $NAME $DIR/usr/share/wordpress/wp-content/${TYPE}s/
rm -R $NAME

FILE=${PACKAGE}_${VERSION}_all.deb

# Формируем пакет
dpkg-deb -b $DIR $FILE >/dev/null 2>&1
rm -R $DIR

echo "$TYPE $NAME $VERSION: OK"
return 0
}

build plugin simple-ldap-login 1.6.0 php-ldap
build theme period 1.270
build plugin the-events-calendar 4.2.7
build plugin wise-chat 2.7
build plugin site-sticky-notes 1.1.0
build plugin so-widgets-bundle 1.9.10
Скрипт состоит из одной функции, которая собственно и занимается сборкой пакета, и вызовов этой функции для сборки нескольких плагинов и тем. Скрипт использует для работы утилиты wget, unzip и dpkg-deb. При отсутствии в системе wget или unzip, их можно установить из одноимённых пакетов.

В скрипте выше указаны версии плагинов, работающие с Wordpress версии 3.6. У плагина simple-ldap-login указана также дополнительная зависимость - пакет php-ldap. Вы можете указать в скрипте свой список плагинов и тем, указав их версии и дополнительные зависимости. При необходимости указать несколько зависимостей, нужно перечислить их через запятую и заключить в кавычки.

воскресенье, 11 августа 2019 г.

Создание своего репозитория Debian при помощи aptly

Несмотря на то, что в официальных репозиториях Debian можно найти почти всё, что может понадобиться, иногда всё-таки приходится самостоятельно собирать пакеты. Я стараюсь не замусоривать систему программами, установленными из исходных текстов и модулями различных языков программирования, установленными через их стандартные инструменты, вроде cpan, pip или gem.

Собирать пакеты приходится по нескольким причинам:
  • В репозитории нет нужного пакета. Иногда бывает можно найти неофициальные репозитории для нужного релиза операционной системы.
  • Через репозитории дистрибутива доступна только старая версия пакета, в которой не хватает нужных функций. В этом случае иногда помогают официальные репозитории backport с более свежими версиями пакетов, собранными для предыдущих релизов операционной системы.
  • В пакете из репозитория есть ошибка. Даже в стабильном дистрибутиве встречаются ошибки, которые не исправляются ментейнерами. Тут придётся либо обновлять пакет, либо исправлять в нём ошибку.
  • В репозитории есть нужная версия пакета, но собранная без поддержки определённой функции. Тут без вариантов - нужно дорабатывать и пересобирать пакет.
Например, в 2013 году я начал настраивать на работе серверы под Debian Wheezy. С тех пор у меня накопилось некоторое количество самосборных пакетов:
  • python-grab - не было в репозитории
  • python-flask-httpauth - не было в репозитории
  • python-pycurl - не было в репозитории
  • libdancer-plugin-database-core-perl - не было в репозитории
  • libnet-ssh-expect-perl - не было в репозитории
  • wordpress-plugin-simple-ldap-login - не было в репозитории
  • python-mysqldb - модуль версии 1.2.3 заменён на более свежую версию 1.2.5 из-за некорректной работы с collation utf8_bin: python-mysqldb: utf8_bin collation will not convert to Unicode strings
  • libsnmp и компания - пакеты были пересобраны из-за того, что не все производители оборудования с поддержкой SNMPv3 обеспечивают монотонный рост счётчика количества перезагрузок и времени с момента перезагрузки: Стандарт SNMPv3 и суровая действительность USM_TIME_WINDOW
  • python-netsnmp - модуль пересобран из-за ошибки в формировании SET-запросов со значениями типа IPv4: Исправление Python-прослойки библиотеки Net-SNMP
  • php5 и компания - среди этих пакетов был пакет php5-snmp, зависящий от пакета libsnmp
  • openntpd - в пакете была иcправлена ошибка в обработке таймаута ответов от серверов DNS: Таймаут DNS в OpenNTPd
  • uwsgi - включена поддержка Linux capabilities: Пересборка uwsgi с поддержкой Linux Capabilities
  • zabbix - собрана более свежая версия пакета, наложена серия нестандартных патчей. Например, добавлены отдельные настройки таймаута и количества повторов для опроса по протоколу SNMP: Установка и настройка Zabbix 2.2.0 в Debian Wheezy
  • python-paramiko и python-ecdsa - модуль paramiko 1.7.7.1-3.1 обновлён до версии 1.16.0-1, т.к. версия paramiko из репозитория имела ошибки в поддержке протокола SFTP: python-paramiko: sftp connections hangs. Модуль python-ecdsa был тоже обновлён, т.к. более новая версия модуля paramiko требовала и более свежую версию модуля ecdsa.
Если у вас есть только один компьютер и вам нужна лишь пара нестандартных программ, то заморачиваться сборкой пакетов, возможно, не стоит. Когда же нестандартных программ набирается с десяток, их приходится периодически обновлять, а потом ещё и устанавливать на десяток компьютеров, то может оказаться проще потратить время на изучение различных способов сборки пакетов и организацию собственного репозитория.

Есть масса способов собрать пакет для Debian:
  • Можно доработать пакет с исходными текстами и собрать как новый пакет с исходными текстами, так и собрать из него новые двоичные пакеты.
  • Для сборки пакетов с модулями python существует утилита python-stdeb: Создание deb-пакетов для модулей Python
  • Для сборки пакетов с модулями perl существует утилита dh-make-perl.
  • Можно попробовать сконвертировать готовый пакет из другого формата при помощи утилиты alien.
  • Можно попробовать собрать пакет при помощи утилиты checkinstall.
  • Наконец, можно просто создать пакет вручную при помощи утилиты dpkg-deb: 5.13 Как мне сделать собственный .deb пакет?
Собственный репозиторий позволяет экономить время, т.к. становится возможным не думать о том, установлены ли в системе пакеты нужных версий со всеми доработками: нужно всего-лишь прописывать в список подключенных репозиториев на каждой системе строчку со своим репозиторием.

Когда утилита aptly ещё не попалась мне на глаза, я делал репозитории при помощи утилиты apt-ftparchive. Не помню подробностей, но точно помню, что была она не очень удобной в использовании.

Потом мне попалась утилита aptly, которая обладала более богатыми возможностями и была более удобна в использовании. Стоит, правда, сказать, что утилита настолько богата возможностями, что удобство использования остаётся довольно относительным. Обновлять репозитории мне приходилось нечасто, поэтому каждый раз я с трудом вспоминал, какими командами я пользовался в прошлый раз. Я стал записывать команды в wiki-страницу и пользоваться своими заметками. Правда, структурированы они были довольно плохо, поэтому в блог я ничего не публиковал. Теперь же решил упорядочить эти заметки и выложить их в блог.

Установка пакета

Установим утилиту:
# apt-get install aptly
Утилита написана на языке go, в скомпилированном виде тянет за собой минимум зависимостей: это утилиты для работы с архивами и утилиты для манипуляции GPG-подписями.

Создание локальных репозиториев

Список репозиториев пока пуст:
$ aptly repo list
No local repositories found, create one with `aptly repo create ...`.
Создадим новый репозиторий с именем stretch:
$ aptly repo create stretch

Local repo [stretch] successfully added.
You can run 'aptly repo add stretch ...' to add packages to repository.

Добавление пакетов в локальный репозиторий

Добавляем в репозиторий stretch все двоичные пакеты из текущего каталога:
$ aptly repo add stretch *.deb
Loading packages...
[+] libauthen-radius-perl_0.26-1ufanet_all added
[+] libnet-ssh-expect-perl_1.09-1_all added
[+] zabbix-agent-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-agent_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-frontend-php_1:3.4.12-1+stretch-ufanet2_all added
[+] zabbix-get-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-get_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-java-gateway_1:3.4.12-1+stretch-ufanet2_all added
[+] zabbix-proxy-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-mysql_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-sqlite3-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-proxy-sqlite3_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-sender-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-sender_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-mysql_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 added
[+] zabbix-server-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 added
Следом добавляем в репозиторий stretch все пакеты с исходными текстами из текущего каталога:
$ aptly repo add stretch *.dsc
Loading packages...
[+] libauthen-radius-perl_0.26-1ufanet_source added
[+] libnet-ssh-expect-perl_1.09-1_source added
[+] zabbix_1:3.4.12-1+stretch-ufanet2_source added
По умолчанию программа размещает пакеты и базу данных в каталоге ~/.aptly

Публикация локальных репозиториев

Список опубликованных репозиториев пока пуст:
$ aptly publish list
No snapshots/local repos have been published. Publish a snapshot by running `aptly publish snapshot ...`.
Если сейчас попытаться опубликовать репозиторий, то программа сообщит о том, что не указан дистрибутив:
$ aptly publish repo stretch ncc
ERROR: unable to publish: unable to guess distribution name, please specify explicitly
Посмотрим текущие настройки репозитория:
$ aptly repo show stretch
Name: stretch
Comment: 
Default Distribution: 
Default Component: main
Number of packages: 23
Укажем дистрибутив репозитория:
$ aptly repo edit -distribution="stretch" stretch
Local repo [stretch] successfully updated.
Опубликуем дистрибутив без PGP-подписи:
$ aptly publish repo -skip-signing=true stretch ncc
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...

Local repo stretch has been successfully published.
Please setup your webserver to serve directory '/home/stupin/.aptly/public' with autoindexing.
Now you can add following line to apt sources:
  deb http://your-server/ncc/ stretch main
  deb-src http://your-server/ncc/ stretch main
Don't forget to add your GPG key to apt with apt-key.

You can also use `aptly serve` to publish your repositories over HTTP quickly.
Посмотрим список опубликованных репозиториев:
$ aptly publish list Published repositories:

ncc/stretch [amd64, source] publishes {main: [stretch]}
Опубликованные репозитории находятся в каталоге ~/.aptly/public, откуда их можно скопировать и разместить на веб-сервере. В примере выше локальный репозиторий stretch был опубликован в каталоге ncc, поэтому найти его можно будет в каталоге ~/.aptly/public/ncc

Поиск и удаление пакетов из локального репозитория

Для поиска пакетов, в имени которых присутствует слово zabbix и версия которых выше 1:3.4, а также пакетов, в имени которых присутствует подстрока -perl, можно воспользоваться следующей командой:
$ aptly package search '(Name (~zabbix), Version (>1:3.4)) | Name (~-perl)'
Как видно, скобки используются для группировки выражений, запятая соответствует логическому И, вертикальная черта соответствует логическому ИЛИ. Тильда обозначает совпадение с регулярным выражением, знак больше используется в своём обычном смысле. Более подробно правила фильтрации описаны в документации на странице: Package queries.

Для удаления пакетов из репозиториев используются те же самые выражения, которые используются при поиске пакетов. Например, удалим все пакеты, в имени которых присутствует подстрока zabbix:
$ aptly repo remove stretch 'Name (~zabbix)'
Loading packages...
[-] zabbix-proxy-mysql_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-sender-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-sqlite3-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-server-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix_1:3.4.12-1+stretch-ufanet2_source removed
[-] zabbix-java-gateway_1:3.4.12-1+stretch-ufanet2_all removed
[-] zabbix-server-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-agent-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-server-mysql_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-get_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-frontend-php_1:3.4.12-1+stretch-ufanet2_all removed
[-] zabbix-sender_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-get-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-server-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-agent_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-sqlite3_1:3.4.12-1+stretch-ufanet2_amd64 removed
[-] zabbix-proxy-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 removed
Для удаления пакетов, в имени которых есть подстрока -perl, можно воспользоваться следующей командой:
$ aptly repo remove stretch 'Name (~-perl)'
Loading packages...
[-] libnet-ssh-expect-perl_1.09-1_all removed
[-] libauthen-radius-perl_0.26-1ufanet_source removed
[-] libauthen-radius-perl_0.26-1ufanet_all removed
[-] libnet-ssh-expect-perl_1.09-1_source removed

Перемещение пакетов между локальными репозиториями

Если пакеты были по ошибке добавлены не в тот репозиторий, то может пригодиться функция перемещения пакетов между репозиториями.

Например, вот так можно выполнить перемещение всех пакетов, у которых в версии фигурирует подстрока stretch, из репозитория wheezy в репозиторий stretch:
$ aptly repo move wheezy stretch 'Version (~stretch)'
Loading packages...
[o] zabbix-server-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-sender_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-pgsql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-get-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-server-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-pgsql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-frontend-php_1:3.4.12-1+stretch-ufanet2_all moved
[o] zabbix-get_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-sqlite3-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-mysql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-server-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-java-gateway_1:3.4.12-1+stretch-ufanet2_all moved
[o] zabbix-server-mysql_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-sender-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-sqlite3_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-agent-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-agent_1:3.4.12-1+stretch-ufanet2_amd64 moved
[o] zabbix-proxy-mysql-dbgsym_1:3.4.12-1+stretch-ufanet2_amd64 moved

Обновление опубликованного репозитория

После первичной публикации репозитория, его нельзя опубликовать повторно. Чтобы изменения в репозитории применились к опубликованному репозиторию, нужно вызвать команду обновления:
$ aptly publish update -skip-signing=true stretch ncc
Loading packages...
Generating metadata files and linking package files...
Finalizing metadata files...
Cleaning up prefix "ncc" components main...
Publish for local repo ncc/stretch [amd64, source] publishes {main: [stretch]} has been successfully updated.

Удаление устаревших пакетов

После удаления пакетов из репозитория, они не удаляются из хранилища автоматически. Во-первых, пакет остаётся в хранилище до тех пор, пока на него ссылается хотя бы один из репозиториев - локальных или опубликованных. Во-вторых, даже если на пакет больше нет ссылок, он не будет удалён до тех пор, пока не будет явно вызвана команда очистки:
$ aptly db cleanup
Loading mirrors, local repos, snapshots and published repos...
Loading list of all packages...
Deleting unreferenced packages (23)...
Building list of files referenced by packages...
Building list of files in package pool...
Deleting unreferenced files (29)...
Disk space freed: 33.61 MiB...
Compacting database...

Удаление опубликованных репозиториев

Для удаления опубликованного репозитория можно воспользоваться соответствующей командой:
$ aptly publish drop stretch ncc
Removing /home/stupin/.aptly/public/ncc/dists...
Removing /home/stupin/.aptly/public/ncc/pool...

Published repository has been removed successfully.
Можно посмотреть в список опубликованных репозиториев и убедиться, что репозитория больше нет среди опубликованных:
$ aptly publish list
No snapshots/local repos have been published. Publish a snapshot by running `aptly publish snapshot ...`.

Удаление локального репозитория

Посмотрим на список локальных репозиториев:
$ aptly repo list
List of local repos:
 * [stretch] (packages: 0)

To get more information about local repository, run `aptly repo show <name>`.
Как видно, в репозитории нет пакетов, поэтому его можно удалить.

Для удаления локального репозитория stretch можно воспользоваться такой командой:
$ aptly repo drop stretch
Local repo `stretch` has been removed.
Посмотрим на список локальных репозиториев снова:
$ aptly repo list
No local repositories found, create one with `aptly repo create ...`.

P. S.

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

Так, на работе всё тот же Debian Wheezy, который вышел в 2013 году, использовался мной аж до 2019 года. За это время успело выйти три новых релиза: Squeezy, Jessie, Stretch. Некоторые коллеги удивлялись, почему я продолжаю использовать этот устаревший релиз. Друге даже не догадывались об этом, т.к. им достаточно, чтобы обновлялся фасад здания, а с ржавыми трубами им дела иметь не приходилось. В 2018 году завершился срок продлённой поддержки Debian Wheezy. В 2019 году репозиторий пропал с зеркал и стало очевидно, что оттягивать обновление больше нельзя. Пришлось отложить всю текущую работу и заняться обновлениями, несмотря молчаливое и явное недовольство окружающих. Нельзя бесконечно копить технический долг - иногда нужно его возвращать.

воскресенье, 4 августа 2019 г.

Установка и настройка Gitea

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

Сборка приложения

Я собираю приложение на Debian Stretch, в котором имеется компилятор go версии 1.7. Для сборки приложения Gitea версии 1.8.1 потребуется компилятор версии 1.9 или выше. Если попытаться собрать приложение компилятором версии 1.7, то сборка завершится такой ошибкой:
package math/bits: unrecognized import path "math/bits" (import path does not begin with hostname)
Для установки компилятора подходящей версии воспользуемся репозиториями с бэкпортами Debian. На момент написания статьи в репозитории имеется компилятор версии 1.11. Пропишем в файл /etc/apt/sources.list репозиторий с бэкпортами для Stretch:
deb http://mirror.yandex.ru/debian stretch-backports main contrib non-free
Обновим список пакетов, доступных через репозитории:
# apt-get update
Установим свежую версию компилятора языка go из репозитория с бэкпортами:
# apt-get install -t stretch-backports golang-go
Если в системе ещё не установлен пакет git, то установим его:
# apt-get install git
Создадим каталог для сборки приложения и перейдём в него:
$ mkdir ~/gitea
$ cd ~/gitea
Скачиваем исходные тексты приложения в созданный каталог:
$ env GOPATH=/home/stupin/gitea/ go get code.gitea.io/gitea
Перейдём в git-репозиторий со скачанными исходными текстами:
$ cd ~/gitea/src/code.gitea.io/gitea
И переключимся на исходные тексты gitea версии 1.8.1:
$ git checkout v1.8.1
Полный список доступных веток и меток релизов можно посмотреть при помощи команд:
$ git branch -a
$ git tag -l
Перед тем, как запустить сборку, исправим ошибку в файле Makefile. В этом файле такую строчку:
export PATH := $($(GO) env GOPATH)/bin:$(PATH)
Нужно заменить на вот такую:
export PATH := $(GOPATH)/bin:$(PATH)
Если этого не сделать, то сборка прервётся с таким вот сообщением об ошибке:
modules/options/options.go:7: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
modules/public/public.go:20: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
modules/templates/templates.go:7: running "go-bindata": exec: "go-bindata": executable file not found in $PATH
Makefile:102: ошибка выполнения рецепта для цели «generate»
make: *** [generate] Ошибка 1
Собираем приложение:
$ env GOPATH=/home/stupin/gitea TAGS="bindata sqlite sqlite_unlock_notify" make generate build
Тег bindata включает сборку единого двоичного файла, включающего в себя все необходимые зависимости и файлы. Теги sqlite и sqlite_unlock_notify включают поддержку базы данных SQLite3. Описание всех тегов можно найти на странице Installation from source, в разделе Build.

В результате сборки в каталоге ~/gitea/src/code.gitea.io/gitea должен появиться файл с именем gitea.

Установка и настройка запуска приложения

Создаём пользователя gitea, от имени которого будет работать приложение:
# useradd -c 'Gitea' -r -M -d /opt/gitea gitea
Создаём каталог приложения gitea и копируем в него только то, что необходимо для работы приложения:
# cd /opt
# mkdir gitea
# cd gitea
# cp /home/stupin/gitea/src/code.gitea.io/gitea/gitea .
Создаём каталог для файла конфигурации:
# mkdir -p cusotm/conf/
Создаём файл /opt/gitea/custom/conf/app.ini с настройками приложения:
[server]
DOMAIN = stupin.su
HTTP_ADDR = 127.0.0.1
HTTP_PORT = 3000
ROOT_URL = https://stupin.su/git/
DISABLE_SSH = false
SSH_PORT = 22
OFFLINE_MODE = false
Чтобы настройки приложения в дальнейшем можно было поменять в процессе первоначальной настройки через веб-интерфейс, дадим пользователю gitea доступ к файлу конфигурации:
# chown gitea:gitea /opt/gitea/custom/conf/app.ini
Создаём service-файл /etc/systemd/system/gitea.ini для запуска/остановки приложения через systemd:
[Unit]
Description=Gitea (Git frontend)
After=syslog.target
After=network.target
;After=postgresql.service
After=nginx.service

[Service]
Type=simple
User=gitea
Group=gitea
WorkingDirectory=/opt/gitea
ExecStart=/opt/gitea/gitea web
Restart=always
Environment=USER=gitea HOME=/opt/gitea
           
[Install]
WantedBy=multi-user.target
Добавляем сервис в автозапуск при загрузке операционной системы:
# systemctl enable gitea.service
И запускаем его прямо сейчас:
# systemctl start gitea.service
Если после запуска сервис переходит в аварийное состояние, стоит заглянуть в журнал /var/log/daemon.log. Поищите сообщение следующего вида:
panic: fail to set message file(en-US): open conf/locale/locale_en-US.ini: no such file or directory
Если такое сообщение есть, то приложение было собрано без тега bindata. Я собирал приложение без этого тега, прежде чем исправил ошибку в файле Makefile. После этого я собрал приложение с тегом bindata, но система сборки не заметила разницы и скомпоновала исполняемый файл из уже готовых объектных файлов. Чтобы очистить имеющиеся файлы и собрать исполняемый файл снова, нужно выполнить следующие команды:
$ cd /home/stupin/gitea/src/code.gitea.io/gitea/
$ env GOPATH=/home/stupin/gitea TAGS="bindata sqlite sqlite_unlock_notify" make clean generate build

Настройка проксирования через nginx

Если приложение планируется запускать на выделенном домене, то для настройки проксирования внешних запросов с https на локальный адрес http при помощи nginx в общем случае нужно создать следующую конфигурацию в файле /etc/nginx/sites-enabled/gitea:
server {
    listen 80;
    server_name example.com;
    return 302 https://$server_name$request_uri;
}

server {
    listen 443 ssl;
    server_name example.com;
    
    ssl_certificate /path/to/certificate.crt;
    ssl_certificate_key /path/to/certificate_key.key;
    
    location / {
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://localhost:3000;
    }
}
И подключить её командой:
# sudo ln -s /etc/nginx/sites-available/gitea /etc/nginx/sites-enabled/gitea
Я же разместил приложение на одном домене с другими приложениями, в отдельном каталоге. Для настройки впишем в файл /etc/nginx/sites-enabled/root настройки проксирования запросов с адреса https://stupin.su/git/ на адрес http://127.0.0.1/git/:
# Gitea
location /git/ {
    proxy_redirect off;
    proxy_bind 127.0.0.1;
    proxy_set_header Host $host;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_pass http://127.0.0.1:3000/;
}

Настройка приложения

Переходим по ссылке https://stupin.su/git/install и задаём настройки:
Настройки базы данных
  Тип базы данных: SQLite3
  Путь: /opt/gitea/data/gitea.db

Основные настройки
  Название сайта: Исходные тексты программ Владимира Ступина
  Путь корня репозитория: /opt/gitea/gitea-repositories
  Корневой путь Git LFS: /opt/gitea/data/lfs
  Запуск от имени пользователя: gitea
  Домен SSH сервера: stupin.su
  Порт SSH сервера: 22
  Gitea HTTP порт: 3000
  Базовый URL-адрес Gitea: https://stupin.su/git/
  Путь к журналу: /opt/gitea/log

Расширенные настройки
  Настройки Email
    Узел SMTP: mail.stupin.su:587
    Отправлять Email от имени: gitea@stupin.su
    SMTP логин: gitea@stupin.su
    SMTP пароль: mailbox_password
    Требовать подтверждения по электронной почте для регистрации: Да
    Разрешить почтовые уведомления: Да
  Сервер и настройки внешних служб
    Включить локальный режим: Нет
    Отключить Gravatar: Да
    Включить федеративные аватары: Нет
    Включение входа через OpenID: Нет
    Отключить самостоятельную регистрацию: Да
    Разрешить регистрацию только через сторонние сервисы: Нет
    Включить саморегистрацию OpenID: Нет
    Включить CAPTCHA: Нет
    Требовать авторизации для просмотра страниц: Нет
    Скрывать адреса электронной почты по умолчанию: Нет
    Разрешить создание организаций по умолчанию: Да
    Включение отслеживания времени по умолчанию:  Да
  Скрытый почтовый домен:
  Настройки учётной записи администратора
    Логин администратора: stupin
    Пароль: 
    Подтвердить пароль:
    Адрес эл. почты: vladimir@stupin.su
После сохранения этих настроек файл /opt/gitea/custom/conf/app.ini примет следующий вид:
APP_NAME = Исходные тексты программ Владимира Ступина
RUN_USER = gitea
RUN_MODE = prod

[server]
DOMAIN           = stupin.su
HTTP_ADDR        = 127.0.0.1
HTTP_PORT        = 3000
ROOT_URL         = https://stupin.su/git/
DISABLE_SSH      = false
SSH_PORT         = 22
OFFLINE_MODE     = false
SSH_DOMAIN       = stupin.su
LFS_START_SERVER = true
LFS_CONTENT_PATH = /opt/gitea/data/lfs
LFS_JWT_SECRET   = xxx

[oauth2]
JWT_SECRET = xxx

[security]
INTERNAL_TOKEN = xxx
INSTALL_LOCK   = true
SECRET_KEY     = xxx

[database]
DB_TYPE  = sqlite3
HOST     = 127.0.0.1:3306
NAME     = gitea
USER     = gitea
PASSWD   = 
SSL_MODE = disable
PATH     = /opt/gitea/data/gitea.db

[repository]
ROOT = /opt/gitea/gitea-repositories

[mailer]
ENABLED = true
HOST    = mail.stupin.su:587
FROM    = gitea@stupin.su
USER    = gitea@stupin.su
PASSWD  = mail_password

[service]
REGISTER_EMAIL_CONFIRM            = true
ENABLE_NOTIFY_MAIL                = true
DISABLE_REGISTRATION              = true
ALLOW_ONLY_EXTERNAL_REGISTRATION  = false
ENABLE_CAPTCHA                    = false
REQUIRE_SIGNIN_VIEW               = false
DEFAULT_KEEP_EMAIL_PRIVATE        = false
DEFAULT_ALLOW_CREATE_ORGANIZATION = true
DEFAULT_ENABLE_TIMETRACKING       = true
NO_REPLY_ADDRESS                  = 

[picture]
DISABLE_GRAVATAR        = true
ENABLE_FEDERATED_AVATAR = false

[openid]
ENABLE_OPENID_SIGNIN = false
ENABLE_OPENID_SIGNUP = false

[session]
PROVIDER = file

[log]
MODE      = file
LEVEL     = Info
ROOT_PATH = /opt/gitea/log
Теперь для пущей безопасности, чтобы никто не подсмотрел в файле пароль почтового ящика mailbox_password и ключи xxx, которыми шифруются сеансовые данные, поменяем права доступа к файлу конфигурации:
# chmod ug=r,o= /opt/gitea/custom/conf/app.ini
Пример страницы обзора списка репозиториев: