Импортозамещение на практике. Часть 6. Пользовательские рабочие места

Моя цель - предложение широкого ассортимента товаров и услуг на постоянно высоком качестве обслуживания по самым выгодным ценам.

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

Какое-то время назад мы с командой тоже приступили к пилотированию миграции на «отечественное» ПО. И вот, что из этого вышло.

Началось все с разговоров. А куда без них?

С чего начинается миграция? С выбора ПО? Нет, с планирования и анализа.

- Чего мы хотим достичь?

- Заместить импортные программные продукты отечественными аналогами.

- Полностью?

- Да.

- А если аналогов нет?

- Тогда в максимально возможном объеме.

И тут у нас встала дилемма. Потому что сотрудники Компании в работе помимо прочего используют CAD и PLM софт, который просто не умеет работать на Linux. Вернее, умеет, но через костыли wine’а, что вызывает недовольство пользователей в плане быстродействия. Так же мы используем VDI Horizon, который умеет работать только с AD. А аналогов у Horizon нет. И не говорите про всякие там надстройки для ProxMox и вот это вот все. Ни одна из других существующих VDI-систем не умеет работать с Instant Clones. Максимум, что предлагает OpenSource (и импортозамещение, которое построено на OpenSource) - это Linked Clones. А у нас на IC построены как Windows пулы, так и Linux. И администрировать VDI-рабочие столы как полноценные машины — это смерти подобно, и стоимость владения вырастает за счет необходимости держать огромный штат техподдержки. Поэтому мы не сможем полностью уйти от Windows, как бы нам этого ни хотелось. И из этого вытекает следующее:

  • Мы не сможем уйти от домена на базе Windows

  • Для обеспечения работоспособности инфраструктуры нужны также сервисы DNS, DHCP, WSUS, CA and etc, которые нет смысла (или невозможно) переносить на Linux.

А раз мы не сможем полностью импортозаместиться, то и проект не имеет особого смысла. На этом можно было бы и закончить, но мы решили, что раз уж инфраструктуру импортозаместить не выйдет — займемся хотя бы пользовательской средой. А со стороны инфры сделаем все, что сможем. Zabbix перевезем на отечественную ОСь.. еще чего-нибудь.. Потому что проблема, например, почтового сервера заключается в том, что Exchange у нас есть, и он работает.. А чтобы его заменить на условный Communigate Pro нам нужно потратить кучу денег. А кто же любит тратить деньги, если и так все работает?.. И так с большинством развернутых в проде систем.

На том и порешали.

0. Ansible

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

И еще один нюанс – динамическое inventory. Клиентские ПК – дело непостоянное: кто-то уволился, кто-то пришел, какие-то ПК выключены, какие-то больше не существуют. И как это поддерживать в актуальном состоянии – целая задача. Особенно, когда речь идет не о десятках, и даже не о сотнях, а о тысячах машин. Мы пошли по более-менее простому пути – использование плагина nmap в связке с плагином constructed для Ansible. При запуске он опрашивает указанные подсети и формирует список хостов для применения плейбуков или ролей, а потом делает свои грязные делишки на отобранные по правилам хосты.

0.1. Муки выбора дистрибутива

Из представленных на рынке «отечественных» ОС по факту выбор-то и не велик, благо, 99% наших рабочих мест не нужно закрывать сертифицированными ОСями. DEB или RPM, Gentoo-подобные поделки для централизованного администрирования даже не рассматривались, спасибо. Астра\Альт\РедОС\ROSA. Проблема в том, что все они платные. Но. У Альта есть форк - Simply Linux, который бесплатен для коммерческого использования. Думаю, дальше не стоит объяснять, почему мы выбрали Simply Linux.

Ниже есть глава про преобразование ISO-образа.

1. Локальный репозиторий

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

Выглядит вот так:

Чтобы попасть на эту страничку, нужно доустановить пакеты ahttpd, alterator-fbi и alterator-mirror, и запустить-активировать ahttpd:

systemctl start ahttpd
systemctl enable ahttpd

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

Дальше мы столкнулись с тем, что нельзя выбрать, куда будут зеркалироваться пакеты. В Вике Альта пишут про то, что надо лезть в fstab, и там монтировать желаемое расположение к /srv/public/mirror*. Но нам это показалось странным. В итоге нашли файлик /usr/lib/alterator/backend3/mirror, в котором указаны пути для alterato-mirror. Поменяли dest_dir=/repos/alt_main-repo и все поехало.

*(Статью в Вике Альта правил я по результатам наших изысканий)

Дальше мы подумали о том, что нам нужен будет кастомный репозиторий, в который мы будем сваливать пакеты того же Каспера и 1С.. да и прочие, которых нет в репозах Альта (Simply). Список доп.софта у нас получился примерно такой:

  • 1С толстый и тонкий клиент

  • Google Chrome

  • Касперский агент и клиент

  • OnlyOffice (ну не Libre же офисным работникам отдавать..)

  • VNC-Viewer

  • VMWare Horizon Client (про него отдельная история, как Windows-админ (я) пытался RPM собрать)

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

В целом – ничего сложного, если не накосячить с деревом каталогов, как я по началу.

После заполнения каталога с RPM-пакетами, в моем случае это /repos/alt_custom-repo/x86_64/RPMS.alt_custom_repo/, нужно создать и подписать индексы:

genbasedir --create --progress --sign --default-key=[gpg-key без скобок] --topdir=/repos/alt_custom-repo x86_64 alt_custom_repo

*genbasedir является командой из пакета apt-utils.

Основные сложности у меня вызвали 2 пакета – OnlyOffice и VMWare Horizon Client. Первый не может удовлетворить зависимости, второй просто не существует.

1.3. Пакеты

Тут я вынужден сделать лирическое отступление - я и моя команда - чистый SRE, парни, которые про эти ваши CI\CD и DevOps практики знают только из интернетов и разговоров с коллегами. Это я к тому, что пересобрать RPM-пакет для сисадмина - это тёмный лес. Но мы не отступаем перед сложностями. Самое сложное было разобраться в скриплетах и найти инфу о том, в какой последовательности они применяются при обновлении пакета. И тут мне помогла вот эта дока.

1.3.1. OnlyOffice

Тут все просто – пересобрать RPM без отсутствующих зависимостей. Но стоит сразу уточнить, что Альт (Simply) по дефолту не даст руту собрать RPM-пакет, для этого предлагается использовать непривилегированного пользователя. Делаем:

cd /tmp
wget https://download.onlyoffice.com/install/desktop/editors/linux/onlyoffice-desktopeditors.x86_64.rpm?_ga=2.238710784.1633530263.1666265796-724860256.1666265796
sudo apt-get install wget
wget https://download.onlyoffice.com/install/desktop/editors/linux/onlyoffice-desktopeditors.x86_64.rpm?_ga=2.238710784.1633530263.1666265796-724860256.1666265796
rpmrebuild -enp onlyoffice-desktopeditors.x86_64.rpm\?_ga\=2.238710784.1633530263.1666265796-724860256.1666265796
sudo apt-get install rpmrebuild
rpmrebuild -enp onlyoffice-desktopeditors.x86_64.rpm\?_ga\=2.238710784.1633530263.1666265796-724860256.1666265796

И в открывшейся спеке комментируем пакеты, которых нет в репозиториях Р10. Приводим к виду:

Requires:      /bin/sh
Requires:      /bin/sh
Requires:      /bin/sh
Requires:      /bin/sh
Requires:      /bin/sh
Requires:      atk
Requires:      boost-filesystem
Requires:      curl
#Requires:      dejavu-sans-fonts
#Requires:      dejavu-sans-mono-fonts
#Requires:      dejavu-serif-fonts
Requires:      gtk3
Requires:      libX11
Requires:      libXScrnSaver
Requires:      liberation-mono-fonts
Requires:      liberation-narrow-fonts
Requires:      liberation-sans-fonts
Requires:      liberation-serif-fonts
Requires:      libstdc++ >= 4.8.0
Requires:      libxcb
#Requires:      rpmlib(CompressedFileNames) <= 3.0.4-1
#Requires:      rpmlib(FileDigests) <= 4.6.0-1
#Requires:      rpmlib(PayloadFilesHavePrefix) <= 4.0-1
#Requires:      rpmlib(PayloadIsXz) <= 5.2-1
#Requires:      xcb-util-image
#Requires:      xcb-util-keysyms
#Requires:      xcb-util-renderutil
#Requires:      xcb-util-wm
#Requires:      xdg-utils

И сохраняем. На выходе получается RPM-пакет без неудовлетворенных зависимостей. Да, так делать неправильно. Но, на Вике Альта так и написано – игнорируй зависимости, и "спина болеть не будет".

Более правильным решением, конечно же, было бы собрать пакет из исходников. Но мы же SRE.. И поэтому это мы записали в планы. На потом.

1.3.2. VMWare Horizon Client

Собрать из исходников проприетарное ПО не получится, даже если вы - не я. Horizon можно скачать в виде tarball-архива с бинарниками, и писать спеку под них. Но я пошел по более простому пути. Я скачал bundle, написал spec-файл для его установки, и запаковал это все в RPM-пакет.

А потом я решил автоматизировать это дело. И написал скриптик, который, получая на вход ссылку на скачивание bundle'а Horizon Client'а, создает структуру каталогов для сборки, меняет в спеке "хххххх" на "версию", и запускает сборку.

Spec-файл Horizon Agent for Linux выглядит так:

Name:           vmware-horizon-client
Version:        xxxxxx
Release:        1
Summary:        custom rpm package vmware-horizon-client

License:        GPL
Source0:        %{name}-%{version}.tar.gz

Requires:       bash
Requires:       python3-modules-sqlite3
Requires:       python3-modules-curses
Requires:       python3-module-pexpect
Requires:       python-modules-json
Requires:       python-module-pygtk
Requires:       python-module-jinja2
Requires:       python-module-yaml
Requires:       python-module-distutils-extra

%description
VMware Horizon Client for LAB.RU team custom repo

%prep
%setup -q

%install
install -d -m 0755 $RPM_BUILD_ROOT/opt/vdi/
install -m 0755 %{name}-%{version} $RPM_BUILD_ROOT/opt/vdi/%{name}-%{version}

%pretrans
if !([ -z "$(pgrep vmware-view)" ]); then
        pkill -f vmware-view
fi

%preun
if !([ -z "$(pgrep vmware-view)" ]); then
        pkill -f vmware-view
fi
file=(`ls /opt/vdi/`)
if !([ -z $file ]); then
        env VMWARE_KEEP_CONFIG=no /opt/vdi/$file -u vmware-horizon-client --console
        rm -f /opt/vdi/$file
fi
if test -f "/etc/skel/Рабочий стол/vmware-view.desktop"; then
        rm -f "/etc/skel/Рабочий стол/vmware-view.desktop"
fi

%posttrans
env TERM=dumb VMWARE_EULAS_AGREED=yes /opt/vdi/%{name}-%{version} --console --set-setting vmware-horizon-integrated-printing vmipEnable yes --set-setting vmware-horizon-html5mmr html5mmrEnable yes --set-setting vmware-horizon-usb usbEnable yes --set-setting vmware-horizon-smartcard smartcardEnable yes --set-setting vmware-horizon-rtav rtavEnable yes --set-setting vmware-horizon-tsdr tsdrEnable yes --set-setting vmware-horizon-scannerclient scannerEnable yes --set-setting vmware-horizon-serialportclient serialportEnable yes --set-setting vmware-horizon-mmr mmrEnable yes --set-setting vmware-horizon-media-provider mediaproviderEnable yes --set-setting vmware-horizon-teams-optimization teamsOptimizationEnable no
echo "view.defaultBroker = 'uag.lab.ru'" > /etc/vmware/view-mandatory-config
echo "view.defaultDomain = 'lab'" >> /etc/vmware/view-mandatory-config
echo "view.defaultProtocol = 'BLAST'" >> /etc/vmware/view-mandatory-config
echo "view.autoHideToolbar = 'TRUE'" >> /etc/vmware/view-mandatory-config
echo "view.autoConnectBroker = 'uag.lab.ru'" >> /etc/vmware/view-mandatory-config
echo "view.enableMMR = 'TRUE'" >> /etc/vmware/view-mandatory-config
chmod +x /usr/share/applications/vmware-view.desktop
if !([[ -f "/etc/skel/Рабочий\ стол/vmware-view.desktop" ]]); then
ln -sf /usr/share/applications/vmware-view.desktop /etc/skel/Рабочий\ стол/vmware-view.desktop
chmod 755 /etc/skel/Рабочий\ стол/vmware-view.desktop
fi
if [[ -d /home/LAB.RU/ ]]; then
        for dir in $(ls /home/LAB.RU/ | grep -v _); do
                if !([[ -f "/home/LAB.RU/$dir/Рабочий\ стол/vmware-view.desktop" ]]); then
                        ln -sf /usr/share/applications/vmware-view.desktop /home/LAB.RU/$dir/Рабочий\ стол/vmware-view.desktop
                        chmod 755 /home/LAB.RU/$dir/Рабочий\ стол/vmware-view.desktop
                fi
        done
fi

%files
/opt/vdi/%{name}-%{version}

%changelog
* August 2022 ANSysoev
-

Скрипт для выглядит так:

#!/bin/bash
if [ -z "$@" ]; then
echo "run script with link to download Horizon Client as argument at run"
else
        cd /home/user/rpmbuild/
        rm -rf /home/user/rpmbuild/*
        mkdir BUILD BUILDROOT SOURCES SPECS SRPMS
        cd /home/user/rpmbuild/SOURCES
        wget "$@"
        version=$(ls | sed -e "s/VMware-Horizon-Client-//" -e "s/.bundle//" -e "s/-/./g")
        name="vmware-horizon-client"
        fullname=$(echo $name-$version)
        mkdir $fullname
        mv $(find /home/user/rpmbuild/SOURCES/  -maxdepth 1 -type f) /home/user/rpmbuild/SOURCES/$fullname/$fullname
        tar -czvf $fullname.tar.gz $fullname
        cp /home/user/vmware-horizon-client.spec /home/user/rpmbuild/SPECS/vmware-horizon-client.spec
        sed -i "s/xxxxxx/$version/" /home/user/rpmbuild/SPECS/vmware-horizon-client.spec
        cd /home/user/rpmbuild/
        rpmbuild -bb SPECS/vmware-horizon-client.spec
fi

Для запуска надо сделать вот такую магию:

horizon-client-RPM-1.sh https://download3.vmware.com/software/CART23FQ3_LIN64_2209/VMware-Horizon-Client-2209-8.7.0-20616018.x64.bundle

И На выходе получится собранный пакетик:

tree
rpmbuild
├── BUILD
│   └── vmware-horizon-client-2209.8.7.0.20616018.x64
│       └── vmware-horizon-client-2209.8.7.0.20616018.x64
├── BUILDROOT
├── RPMS
│   └── x86_64
│       └── vmware-horizon-client-2209.8.7.0.20616018.x64-1.x86_64.rpm
├── SOURCES
│   ├── vmware-horizon-client-2209.8.7.0.20616018.x64
│   │   └── vmware-horizon-client-2209.8.7.0.20616018.x64
│   └── vmware-horizon-client-2209.8.7.0.20616018.x64.tar.gz
├── SPECS
│   └── vmware-horizon-client.spec
└── SRPMS

1.3.3. Google Chrome and etc.

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

2. Преобразование ISO-образа

Мы выбрали Simply, потому что он бесплатный.

Но. Отдавать офисным работникам Linux с xfce на борту — это парализовать работу офиса на неопределенный срок. Если более-менее привычные к ПК ребята освоятся достаточно быстро, но тоже не сразу, то тётушки-бухгалтеры — скорее никогда, чем быстро. Поэтому что? Правильно, надо поставить KDE! Это тоже не решит все проблемы, но порог вхождения существенно снизится.

Но. Если выпилить xfce и поставить сверху KDE — получится не кошерно. Останутся лишние пакеты от DM и DE, поэтому надо лезть в ISO и понять, как же происходит установка Linux, чтобы вмешаться в этот процесс и вместо одной DE поставить другую.

На самом деле ничего сложного в этом нет (если разобраться), но все по порядку.

Прочитав Вику Альта Модификация установочного ISO образа, Создание образов устройств, Autoinstall, и ничего в ней не поняв, я решил отвязаться от дистрибутива, и полез в теорию. Хотя последняя ссылка мне помогла во многом.

Что есть дистрибутив Linux? Дистрибутив – это, сильно упрощая, ядро Linux + набор пакетов. Всё. Набор пакетов ПО – суть «встроенный в ISO репозиторий». У Simply пакеты лежат в \ISOroot\ALTLinux\RPMS.main\. Чтобы собрать дистрибутив, нужно набрать выбранные пакеты, удовлетворить их зависимости, и положить в ISO, описать «группы ПО», пересчитать и подписать индексы. . В директории Metadata\pkg-groups.tar\lists\ лежит файл .base, содержащий в себе список пакетов, которые накатываются в любом случае, а так же директория slinux, содержащая в себе описание этих самых групп ПО (имя и состав пакетов каждой группы).

Для того, чтобы избавиться от xfce и добавить вместо него KDE есть 2 пути: сложный и очень сложный.

Очень сложный предполагает скачивание из репозитория нужных пакетов (KDE и его зависимости). Но мне было лень. Наверное, есть какой-то специальный софт, который умеет считывать зависимости из метадаты указанного пакета, и выкачивать эти зависимости.. Но я такого не нашел, поэтому пошел по сложному пути, а именно — скачал Альт Workstation 10 K (у которого та же пакетная база, что и у Simply, только с KDE искаропке), и подменил пакеты в ISO-репозитории и файл .base, собрал, установил… … … … ..ииииии.. получил свежеустановленный Альт 10 K. Удивился. Много думал. В основном о том, что моих теоретических познаний явно недостаточно для реализации поставленной мной же задачи. (Как же сложно и неприятно быть тупым! Проклятый COVID!)

После пролитых слез, покрасневших глаз, вырванных волос и обгрызенных ногтей поиска причин, я понял, что в списке пакетов есть RPM’ки со словом branding в имени, и понял, что именно их установка в целевую систему отвечает за то, какой дистрибутив получится на выходе. Например, один из пакетов правит файлик/etc/system-release, другой накидывает нескучные обои и т.д.

Словом, после нехитрых, но скучных и утомительных правок файлика .base и закидывания в репозиторий недостающих пакетов (у меня на это ушло примерно 10 попыток установки системы), я получил на выходе Simply Linux 10 K. Потом я решил, что этого недостаточно, и отсобрал еще один ISO уже без DE.. Simply Linux Server. А то на чем мне Ansible и локальный репозиторий разворачивать?

Список команд чуть ниже.

Далее — доставка на свежеустановленную систему ключей Ansible и установка hostname. Если в ОСи будут установлены ключи от Ansible, то дальше можно все сделать через сам Ansible. Тут нам поможет пакет alterator-postinstall и простейший скрипт, который доставит в /root/.ssh/authorized_keys нужные ключи. У меня 2 Ansible, 1 в головном офисе, и 1 в филиале, канал с которым, мягко говоря, оставляет желать лучшего. Поэтому 2 ключа. Так же нужно позаботиться об инженерах техподдержки, и закинуть в свежую систему скрипт, который на основе существующих в AD записей для компьютеров, будет подбирать подходящий hostname для нового ПК перед вводом в домен. Эти скрипты тоже нужно будет доставить в целевую систему. Скрипт назовем 87-set-ansbls-keys.sh, и напишем в нем следующее (ну почти так, ключи я вам не покажу =Ъ):

#!/bin/sh

. install2-init-functions

echo "ssh-rsa бла-бла-бла-набор-символов root@ansible-filial-hostname" > $destdir/root/.ssh/authorized_keys
echo "ssh-rsa бла-бла-бла-набор-символов root@ansible-hostname" >> $destdir/root/.ssh/authorized_keys

cp /var/ChangeHostName.py $destdir/var/

Тут одна тонкость — таргет указывается с преффиксом $destdir, иначе установщик, выполняя директиву postinstall, запишет ключи в свой /root, а не в устанавливаемую ОС.

Скрипт поиска подходящего hostname прост, как 5 копеек (которых никто не видел уже черт знает сколько лет). Я создал в AD бесправную учетку, чтобы Python мог сходить в AD и считать уже существующие в определенной OU учетки компьютеров, и выбрать следующий по списку.

#!/usr/bin/python3
# -*- coding: utf-8 -*-
import os
import sys
from getpass import getpass
from ldap3 import Server, Connection, SUBTREE, LEVEL
import time

username="lab.ru\linux_to_domain"
password="Passw0rd!"

server = Server("dc-1.lab.ru", port=389, use_ssl=False, get_info='ALL')
connection = Connection(server, user=username, password=password,
               fast_decoder=True, auto_bind=True, auto_referrals=True, check_names=False, read_only=True,
               lazy=False, raise_exceptions=False)

hostnamedigit=1
hostname = "ARM-"+'{:0>4}'.format(hostnamedigit)

def get_all_ad_hosts(connection):

    results = list()
    elements = connection.extend.standard.paged_search(
        search_base='OU=LINUX,OU=Computers,dc=lab,dc=ru',
        search_filter='(&(objectCategory=computer)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))',
        search_scope=SUBTREE,
        attributes=['name'],
        paged_size=100)
    for element in elements:
        host = dict()
        if 'dn' in element:
            host['dn'] = element['dn']
            host['name'] = element['attributes'][u'name'][0]
            results.append(host)
    return(results)
    connection.unbind()

def search_for_duplicatename(hostname,list_of_computers):
    for computer in list_of_computers:
        if computer['name'].casefold() == hostname.casefold():
            print(hostname+" already exists")
            return 1
    return 0

computers = get_all_ad_hosts(connection)
while search_for_duplicatename(hostname, computers) != 0:
    hostnamedigit += 1
    hostname = "ARM-"+'{:0>4}'.format(hostnamedigit)
print(hostname)
os.system("hostnamectl set-hostname "+hostname)
print("Your system is gonna reboot in 10 seconds....")
time.sleep(10)
os.system("reboot now")

Теперь о том, куда же эти скрипты поместить. Целевая директория — архив altinst, находящийся в корне ISO. В архиве скрипт нужно расположить в директории /usr/share/install2/postinstall.d/ и не забыть сделать его исполняемым, иначе чуда не произойдет. Скрипт подбора hostname я положил в /var, хотя это не играет особой роли.

Но. Прежде, чем все это сделать, нужно смонтировать скачанный ISO, скопировать куда-то его содержимое, распаковать архив с помощью squashfs-tools (пакет нужно поставить), скопировать скрипты, поменять файлик .base.. Словом, сделать все свои грязные делишки, и потом запаковать архив и собрать ISO. Итого (перед выполнением genbasedir нужно скопировать необходимые пакеты, удалить лишние, и привести в порядок файлы, отвечающие за установку групп ПО, как было описано выше):

apt-get install apt-utils squashfs-tools
mkdir /home/user/slinux_iso /home/user/altinst
cd /home/user/altinst
sudo mount /home/user/slinux.iso /mnt
cp -r /mnt/* /home/user/slinux_iso/
unsquashfs /home/user/slinux_iso/altinst
cp /path_to_script_ansible_keys.sh /home/user/altinst/squashfs-root/usr/share/install2/postinstall.d/87-set-ansbls-keys.sh
chmod +x /home/user/altinst/squashfs-root/usr/share/install2/postinstall.d/87-set-ansbls-keys.sh
cp /path_to_script_hostname /home/user/altinst/squashfs-root/var/ChangeHostName.py
chmod +x /home/user/altinst/squashfs-root/var/ChangeHostName.py
mksquashfs /home/user/altinst/squashfs-root/ /home/user/slinux_iso/altinst
genbasedir --create --topdir=/home/user/slinux_iso/ ALTLinux main
sudo xorriso \
-as mkisofs \
-joliet \
-partition_cyl_align "off" \
-partition_offset 16 \
-iso-level 3 \
-full-iso9660-filenames \
-sysid "LINUX" \
-volid "Simply Linux 10.1 x86_64" \
-volset "ALT" \
-publisher "BASEALT LTD" \
-appid "SIMPLY LINUX 10.1 X86_64 2022-06-28" \
-copyright "LICENSE_ALL_HTML" \
-eltorito-boot syslinux/isolinux.cfg \
-eltorito-catalog syslinux/boot.cat \
-no-emul-boot \
-boot-load-size 4 \
-boot-info-table \
-isohybrid-mbr syslinux/isohdpfx.bin \
-eltorito-alt-boot \
-e EFI/.efiboot.img \
-no-emul-boot \
-isohybrid-gpt-basdat \
-output ../SimplyLinux10_KDE.iso

И ждем.

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

3. Ansible playbooks

Дальше мы приступили к написанию плейбуков для доставки на клиенты нужных настроек:

  • ssh

  • sssd

  • samba

  • kerberos

  • pam

  • cups

  • ввод в домен

  • … etc.

Я довольно долго думал о том, нужно ли приводить листинг плейбуков. И, посоветовавшись с командой, мы решили, что не все стоит выкладывать. По ряду причин.

Спойлер

Да, мы знаем, что в нижеследующем можно сделать все иначе. Использовать роли, переменные и т.д. Хранить все в гите, отслеживать там изменения и прочее, прочее, прочее. Мы знаем, спасибо. Но читать это в рамках статьи будет невозможно. Поэтому плейбуки. Поэтому разбито так, как разбито.

 Итак, требования следующие:

  1. Доставить внутренние сертификаты, импортировать их

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

  3. Установить в систему весь требуемый софт, закинуть конфиги для установленного софта, настроить skel, чтобы при первом входе в систему, пользователь получил нужные линки и каталоги, предусмотреть подключение для пользователей сетевых шар, живущих на Windows-сервере, предусмотреть подключение пользователям списка баз 1С, как это описано здесь (ну это просто удобно)

  4. Сформировать конфиги для подключения ПК к домену, предусмотреть запуск логон-скриптов, обойти проблему авторизации на proxy-сервере (в нашем случае это скрипт, который стучится в proxy-сервер с Kerberos-тикетом, посредством curl’а, и получает на проксе тикет на 8 часов). Да, WSA – она такая..

  5. Настроить ssh согласно требованиям от ИБ, в том числе ограничить доступ для определенных групп AD

  6. Настроить доступ к sudo для определенной группы AD

  7. Соблюсти прочие требования ИБ (мне запретили это показывать. ИБ – те еще параноики)

  8. Ввести ПК в домен

  9. Предусмотреть механизм «брендирования» - обои рабочего стола, загрузчика и прочее (это мне тоже запретили вам показывать)

  10. Предусмотреть возможность массового обновления

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

«Кто в теме и так поймет, кто не в теме – тот все равно листинг читать не будет».

Оговорюсь лишь о том, что для доставки файлов, открытых ключей и прочего, мы опубликовали их рядом с репозиториями. Там все равно web-сервер поднят, почему бы его не использовать?

3.1. Доставить внутренние сертификаты, импортировать их

- name: Install local CA certs
  gather_facts: false
  hosts: simply
  tasks:
    - name: Execute script
      shell: |
        mkdir /tmp/certs
        cd /tmp/certs
        wget --no-check-certificate https://local-repo-srv.lab.ru/alt_custom-repo/certs/root.crt
        wget --no-check-certificate https://local-repo-srv.lab.ru/alt_custom-repo/certs/subca.crt
        cp ./rootca.crt /etc/pki/ca-trust/source/anchors/
        cp ./subca.crt /etc/pki/ca-trust/source/anchors/
        chmod a-x /etc/pki/ca-trust/source/anchors/*
        update-ca-trust extract

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

- name: add repositories
  gather_facts: false
  hosts: simply
  tasks:
    - name: delete all /etc/apt/sources.list.d/
      shell: rm -f /etc/apt/sources.list.d/*

    - name: create lab.list
      copy:
        dest: /etc/apt/sources.list.d/lab.list
        content: |
          rpm [p10] http:// local-repo-srv.lab.ru /alt_main-repo p10/branch/x86_64 classic
          rpm [p10] http:// local-repo-srv.lab.ru /alt_main-repo p10/branch/noarch classic
          rpm [alt_custom_repo] http:// local-repo-srv.lab.ru /alt_custom-repo x86_64 alt_custom_repo

    - name: add custom gpg key
      shell: curl http:// local-repo-srv.lab.ru /alt_custom-repo/x86_64/base/custom_repo.pgp >> /etc/apt/custom_repo.pgp && gpg --no-default-keyring --keyring /usr/lib/alt-gpgkeys/pubring.gpg --import /etc/apt/custom_repo.pgp

    - name: add /etc/apt/vendors.list.d/lab.list
      copy:
        dest: /etc/apt/vendors.list.d/lab.list
        content: |
          simple-key "alt_custom_repo" {
          Fingerprint "бла-бла-бла-буквы-и-цЫфры";
          Name "Vasily <Vasya@lab.ru>";
          }

    - name: apt-get update
      shell: |
apt-get update
	apt-get dist-upgrade -y

3.3. Установить в систему весь требуемый софт, …

- name: soft installation
  gather_facts: false
  hosts: simply
  tasks:

    - name: update
      shell: apt-get update -y

    - name: install packages
      apt_rpm:
        name:
          - sudo
          - apt-scripts
          - openssh
          - task-auth-ad-sssd
          - sssd-ad
          - samba-client
          - 1c-preinstall-full
          - vmware-view-preinstall
          - onlyoffice-desktopeditors
          - nano
          - firefox
          - libinput
          - libinput-devel
          - xorg-drv-libinput
          - xorg-drv-libinput-devel
          - x11vnc
          - x11vnc-service
          - 1c-enterprise-8.3.18.1483-thin-client
          - vlc
          - google-chrome-stable
          - autofs
          - vmware-horizon-client
          - system-config-printer
          - kde5-spectacle
          - evolution
          - evolution-ews
          - conky
          - remmina
          - remmina-plugins
          - cups
        state: present

    - name: remove Libre, stop cups
      shell: |
            apt-get remove libreoffice5 -y && apt-get clean -y && apt-get autoremove -y
            systemctl stop cups

#для VNC есть еще таска для установки пароля, но я вам ее не покажу.
#Там тривиально
    - name: x11vnc config
      copy:
        dest: /usr/sbin/x11vnc-start-daemon
        content: |
          #!/bin/bash
          AUTH=`ps aux | grep "\-auth " | head -n 1`
          AUTH=${AUTH/*\-auth /}
          AUTH=${AUTH/ */}
          /usr/bin/x11vnc -auth $AUTH -dontdisconnect -usepw -shared -forever -rfbport 5900 -rfbauth /etc/vncpasswd -display :0 -repeat

    - name: catalogs and files
      file:
        path: "{{ item.path }}"
        state: "{{ item.state }}"
      with_items:
        - { path: /etc/skel/Рабочий стол/, state: directory } #каталог для создания ярлыков
        - { path: /mnt/share/, state: directory } #каталог для монтирования «сетевых дисков»
        - { path: /var/ChangeHostName.py, state: absent } #удаление скрипта подбора hostname
        - { path: /opt/1cv8/x86_64/8.3.18.1483/libstdc++.so.6, state: absent } #для работы 1С этот файл надо удалить. Не спрашивайте, это не баг, это фича.
        - { path: /etc/skel/.1C/1cestart/, state: directory } #каталог для монтирования шары со списком баз для 1С

    - name: create links
      file:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
        state: "{{ item.state }}"
        mode: "{{ item.mode }}"
        force: yes
      with_items:
        - { src: /mnt/share/, dest: /etc/skel/Рабочий стол/Сетевые_Папки, state: link, mode: '755' }
        - { src: /usr/share/applications/firefox.desktop, dest: /etc/skel/Рабочий стол/firefox.desktop, state: link, mode: '755' }
        - { src: /usr/share/applications/google-chrome.desktop, dest: /etc/skel/Рабочий стол/google-chrome.desktop, state: link, mode: '755' }
        - { src: /usr/share/applications/1cestart-8.3.18-1483.desktop, dest: /etc/skel/Рабочий стол/1C.desktop, state: link, mode: '755' }
        - { src: /usr/share/kf5/applications/kf5/org.kde.dolphin.desktop, dest: /etc/skel/Рабочий стол/Dolphin.desktop, state: link, mode: '755' }
        - { src: /usr/share/applications/onlyoffice-desktopeditors.desktop, dest: /etc/skel/Рабочий стол/onlyoffice-desktopeditors.desktop, state: link, mode: '755' }
        - { src: /usr/share/applications/vmware-view.desktop, dest: /etc/skel/Рабочий стол/vmware-view.desktop, state: link, mode: '755' }
        - { src: /mnt/.services/1CBases/1cestart_alt.cfg, dest: /etc/skel/.1C/1cestart/1cestart.cfg, state: link, mode: '755' }

    - name: copy files
      copy:
        src: "{{ item.src }}"
        dest: "{{ item.dest }}"
        owner: "{{ item.owner }}"
        group: "{{ item.group }}"
        mode: "{{ item.mode }}"
      with_items:
#блок копирования настроек cups. Они для всех одинаковы, подключается очередь
#печати на принтер MyQ
        - { src: /etc/ansible/playbooks/files/cups/cupsd.conf, dest: /etc/cups/cupsd.conf, owner: root, group: lp, mode: '640' }
        - { src: /etc/ansible/playbooks/files/cups/cups-files.conf, dest: /etc/cups/cups-files.conf, owner: root, group: root, mode: '644' }
        - { src: /etc/ansible/playbooks/files/cups/printers.conf, dest: /etc/cups/printers.conf, owner: root, group: lp, mode: '600' }

    - name: enable services
      service:
        name: "{{ item }}"
        enabled: yes
        state: restarted
      with_items:
        - x11vnc
        - cups

    - name: firefox set krb enable
      copy:
        dest: /usr/lib64/firefox/browser/defaults/preferences/myprefs.js
        content: |
          pref("network.negotiate-auth.trusted-uris",".lab.ru");
          pref("network.automatic-ntlm-auth.trusted-uris",".lab.ru");
          pref("network.automatic-ntlm-auth.allow-non-fqdn","true");
          pref("network.negotiate-auth.allow-non-fqdn","true");
          pref("network.negotiate-auth.delegation-uris",".lab.ru");

    - name: chrome set krb enable
      copy:
        dest: /etc/opt/chrome/policies/managed/krb.json
        content: |
          {
            "AuthServerAllowlist": "*.lab.ru",
            "AuthNegotiateDelegateAllowlist": "*.lab.ru"
          }

    - name: apt dedup, enable cups
      shell: |
             apt-get dedup -y
             systemctl start cups

3.4. Сформировать конфиги для подключения ПК к домену, ..

- name:  pre-domain config
  gather_facts: false
  hosts: simply
  tasks:
          - name: krb config
            copy:
              dest: /etc/krb5.conf
              content: |
                [logging]
                # default = FILE:/var/log/krb5libs.log

                [libdefaults]
                 default_realm = LAB.RU
                 dns_lookup_realm = true
                 dns_lookup_kdc = true
                 ticket_lifetime = 24h
                 renew_lifetime = 7d
                 rdns = false
                 forwardable = yes
                 default_ccache_name = FILE:/tmp/krb5cc_%{uid}

          - name: samba config
            copy:
              dest: /etc/samba/smb.conf
              content: |
                [global]
                security = ads
                realm = LAB.RU
                workgroup = LAB
                netbios name = {{inventory_hostname}}
                template shell = /bin/bash
                kerberos method = system keytab
                wins support = no
                idmap config * : range = 10000-20000000
                idmap config * : backend = tdb

          - name: sssd config
            copy:
              dest: /etc/sssd/sssd.conf
              content: |
                [sssd]
                config_file_version = 2
                user = root
                domains = LAB.RU
                services = pam,nss,autofs

                [nss]

                [pam]

                [domain/LAB.RU]
                id_provider = ad
                auth_provider = ad
                chpass_provider = ad
                default_shell = /bin/bash
                fallback_homedir = /home/%d/%u
                ad_server = dc-1.lab.ru,dc-2.lab.ru
                ad_backup_server = _srv_
                cache_credentials = true
                debug_level = 2
#монтирование сетевых дисков. Через pam mount ничего не вышло. Он либо багованый,
#либо фича у него такая, но мы перешли на смб, который монтирует шары при
#обращении к ним пользователя 
          - name: autofs config
            copy:
              dest: /etc/auto.master
              content: |
                /mnt/share        /etc/auto.samba --ghost
                /mnt/.services    /etc/auto2.samba --ghost --timeout 60


          - name: autofs config 1
            copy:
              dest: /etc/auto.samba
              content: |
                disk_1  -fstype=cifs,multiuser,cruid=$UID,sec=krb5,domain=LAB.RU      ://dfs-server.lab.ru/Share
                disk_2  -fstype=cifs,multiuser,cruid=$UID,sec=krb5,domain=LAB.RU      ://file-server.lab.ru/Share2
                disk_3  -fstype=cifs,multiuser,cruid=$UID,sec=krb5,domain=LAB.RU      ://file-server.lab.ru/Share3

          - name: autofs config 2
            copy:
              dest: /etc/auto2.samba
              content: |
                1CBases      -fstype=cifs,multiuser,cruid=$UID,sec=krb5,domain=LAB.RU      ://file-server.lab.ru /1CBases
                background   -fstype=cifs,multiuser,cruid=$UID,sec=krb5,domain=LAB.RU      ://file-server.lab.ru/background


          - name: enable autofs
            service:
              name: autofs
              enabled: yes
              state: restarted

          - name: configure nsswitch and cronyd
            lineinfile:
              path: "{{ item.path }}"
              regexp: "{{ item.regexp }}"
              line: "{{ item.line }}"
            loop:
              - { path: /etc/nsswitch.conf, regexp: '^passwd', line: 'passwd:     files sss' }
              - { path: /etc/nsswitch.conf, regexp: '^shadow', line: 'shadow:     tcb files sss' }
              - { path: /etc/nsswitch.conf, regexp: '^group', line: 'group:      files sss' }
              - { path: /etc/chrony.conf, regexp: '^pool', line: 'pool dc-1.lab.ru iburst' }

          - name: set control policy and system-auth
            shell: |
                  control sudo public
                  control system-auth sss

#Cisco WSA – довольно «интересный» proxy-сервер..
#И так как далеко не все Linux’овые приложения умеют использовать krb-тикеты
#для авторизации на прокси, приходится использовать костыль. Нет, можно было
#заставить пользователя сначала запустить браузер, авторизоваться на проксе,
#и только после этого получить доступ в интернет, скажем, с мессенджера..
#но мы посчитали это издевательством.

          - name: proxy auth script
            copy:
              dest: /var/proxy_auth.sh
              content: |
                #!/bin/bash
                ip=$(echo `ifconfig eth0 2>/dev/null|awk '/inet addr:/ {print $2}'|sed 's/addr://'`)
                echo "curl -isL --negotiate -u : https://proxy-server.lab.ru/same_text/$ip/http://lab.ru/ > /dev/null" > /tmp/proxy_auth.sh
                /bin/bash /tmp/proxy_auth.sh
                rm -f /tmp/proxy_auth.sh
              mode: "755"

          - name: create logon script fpr proxy auth
            copy:
              dest: /etc/profile.d/proxy_auth.sh
              content: |
                #!/bin/bash
                /var/proxy_auth.sh
              mode: "755"

3.5. Настроить ssh согласно требованиям от ИБ, в том числе ограничить доступ для определенных групп AD

- name: ssh
  gather_facts: false
  hosts: simply
  tasks:
          - name: edit sshd config
            lineinfile:
                    path: /etc/openssh/sshd_config
                    regex: "^(#)?{{item.key}}"
                    line: "{{item.key}} {{item.value}}"
                    state: present
            loop:
                            - { key: "LogLevel", value: "VERBOSE" }
                            - { key: "PermitRootLogin", value: "prohibit-password" }
                            - { key: "MaxAuthTries", value: "3" }
                            - { key: "MaxSessions", value: "2" }
                            - { key: "PermitEmptyPasswords", value: "no" }
                            - { key: "UsePAM", value: "yes" }
                            - { key: "AllowGroups", value: "domain?users root wheel linux-sudoers" }
#да, да, именно в таком формате тут нужно указывать доменные группы с пробелами
#в названиях
            notify:
                    - restart sshd
                    - enable sshd

  handlers:
          - name: restart sshd
            service:
                    name: sshd
                    state: restarted

          - name: enable sshd
            service:
                    name: sshd
                    enabled: yes

3.6. Настроить доступ к sudo для определенной группы AD

- name: sudoers
  gather_facts: false
  hosts: simply
  tasks:
          - name: edit sudoers file
            blockinfile:
                    path: /etc/sudoers
                    backup: yes
                    block: |
                            %Linux-Sudoers ALL=(ALL) ALL
                            %Linux-Users ALL=/usr/bin/apt-cache
                            %Linux-Users ALL=/usr/sbin/poweroff
                            %Linux-Users ALL=/usr/sbin/NetworkManager
                    validate: /usr/sbin/visudo -cf %s

          - name: replace line
            lineinfile:
                    path: /etc/sudoers
                    regexp: '^@includedir /etc/sudoers.d'
                    line: '#@includedir /etc/sudoers.d'
                    validate: /usr/sbin/visudo -cf %s

3.8. Ввести ПК в домен

#При запуске спрашивает логин и пароль (в «приватном» виде).
#После чего получает керберос-тикет и подключает ОС к домену
- name: domain join
  gather_facts: false
  hosts: simply

  vars_prompt:
    - name: "adlogin"
      prompt: "Enter AD Login"
      private: no

    - name: "password"
      prompt: "Enter password"
      private: yes

  tasks:
    - name: domain check
      shell: timeout 6s net ads testjoin
      register: domain_state
      failed_when: domain_state.rc == 0

    - name: Clear the sssd cache
      shell: rm -f /var/lib/sss/db/* /var/lib/sss/mc/*

    - name: get krb ticket
      shell: echo '{{ password }}'| kinit "{{ adlogin }}"

    - name: join domain
      command: net ads join -U "{{ adlogin }}"%"{{ password }}" createcomputer="/Computers/Linux"

    - name: sssd enable
      service:
        name: sssd
        enabled: yes
        state: restarted

    - name: reboot
      reboot:
        reboot_timeout: 120

3.10. Предусмотреть возможность массового обновления

Тут пришлось сделать отдельный playbook для обновления, и отдельный playbook для брендирования, так как мы пошли по простому пути – не стали пилить тему для кедов, а просто поменяли интересующие нас картинки. И поэтому при обновлении пакетов картинки затираются. Поэтому сразу после обновления происходит брендирование.

Playbook апдейта:

- name: update and upgrade
  hosts: simply
  gather_facts: false
  tasks:

  - name: update & upgrade
    shell: |
      apt-get update -y && apt-get dist-upgrade -y
      apt-get dedup -y

Брендинг у нас уже был (но я вам его не покажу, мне запретили). И поэтому playbook обновления выглядят так:

- import_playbook: update.yml
- import_playbook: branding.yml

4. Server-side

Следующий этап импортозамещения – перенос инфраструктурных сервисов на «отечественные» программные продукты. Об этом мы долго думали, рассматривали варианты, поднимали стенды (но стенд и боевые тесты – это совсем не одно и то же). Словом, перекинуть на Альт или Астру, или какую-то еще ОС, например, Zabbix, dhcp, dns, proxy-сервер – не проблема. Это все есть и работает. Но заменить VMWare Hypervisor просто нечем. OpenNebula? Ну да, ну да. KVM? ProxMox? VDI – отсутствует. Проблема «отечественного» софта осталась той же – это пересобранное OpenSource ПО, которое стоит денег. И как весь OpenSource, его стоимость заключается не в «коробка + 2 инженера», а в «десяток инженеров с зарплатами сильно выше, чем у «коробочного» (в зависимости от размера инфраструктуры).

По итогу мы перекинули на Simply Linux продуктовый Zabbix, и подняли Ansible, jitsi и локальный репозиторий. Дальше по плану перенос Nextcloud, Kaspersky Security Center, Certification Authority. Но все это.. такое.. Фикция и профанация.

5. Выводы

В целом, со слов тестовой группы, работать на Linux «можно». Да, недостает привычных виндовых удобств, типа SSO для Outlook, предпросмотра документов в 1С (над этим работает наш центр компетенций 1С), «адок с банк-клиентами и криптопрой» (с) техподдержка.

«Работать можно». Базовый функционал есть. Да, не искаропке, его приходится допиливать, ибо парадигма Linux такова, что каждый каждый делает «это» так, как хочет. Если вы понимаете, о чем я. А допиливать функционал приходится в основном силами админов и ТП.

В итоге: используя данный инструментарий, сотрудник сможет выполнять свои обязанности.

Но. Шаг влево\шаг вправо вызывает жуткий butthurt как у пользователя, так у техпода, привыкшего к Windows. У нас же как заведено? Логи начинают читать только тогда, когда google ничего не подсказал. И перестроить мышление довольно трудно, потому что «ну там же так работает! И это удобно! Так зачем уходить от удобного и привычного к сложному и непонятному?». Но это уже политика, это на другом ресурсе обсуждается.

«Мыши плакали и кололись..» А, стоп, где-то я это уже писал..

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.
А вы уже импортозамещаете?
0% Импортозамещаем 0
0% Нет, мы в домике. 0
Никто еще не голосовал. Воздержавшихся нет.
Источник: https://habr.com/ru/post/695284/


Интересные статьи

Интересные статьи

Cегодня мы рассмотрим две важные темы по реализации in-app покупок с Google Billing Library. Начнем с получения активных покупок пользователя, то есть действующих подписок и ранее купленных non-consum...
По ходу разработки генератора кода для виртуальной машины понял, что виртуальная машина не готова к полноценным вызовам функций, с передачей аргументов и хранению локальн...
На рубеже четвертого и третьего тысячелетия до нашей эры на Земле возникли две первые цивилизации. В долине Нила после объединения верхнего и нижнего Египта образовалось ...
Так сложилось, что за последние 18 лет, не приходилось писать на C/C++. На работе использовалась Java, да и ввиду должностей деятельность больше была связана с предприним...
Продолжение цикла статей. Предыдущие посты серии: Часть 6 Часть 5 Часть 4 Часть 3 Часть 2 Часть 1 В прошлый раз, после установки бюджетного лидара RPlidar-A1 удалось построить карту пом...