Службы каталогов играют важную роль в ИТ-инфраструктуре любой организации. Каждая компания использует службу каталогов по-своему, отсюда возникает необходимость в ее адаптации под уникальные условия эксплуатации. В этой статье рассмотрим программный комплекс ALD Pro от «Группы Астра» — как встроенные возможности, так и примеры его кастомизации под требования заказчиков.   

Привет, Хабр! Меня зовут Александр Усов, я работаю системным инженером в K2Tex. Когда я рассказываю заказчикам про ALD Pro, обычно сразу уточняю: это не жёсткая конструкция, а скорее продвинутая система, которую можно и нужно подстраивать под свои задачи. Сам по себе продукт уже мощный и гибкий, а вендор специально оставил место для кастомизации. Вот как раз этим мы в K2Тех активно и пользуемся. У нас накопилась технологическая экспертиза, и мы знаем, где продукт стоит дополнить, а где — адаптировать под конкретные запросы бизнеса. Моя задача — не просто внедрить решение и оставить заказчика наедине со сложной системой, а сделать так, чтобы он получил инструмент, идеально подходящий именно ему.

Что такое ALD Pro

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

У «Группы Астра» есть блог на Хабре, так что можете почитать ещё и вот эти посты:  

Мы в K2Tex используем ALD Pro при создании импортонезависимых ИТ-инфраструктур заказчиков. Когда аппаратная часть инфраструктуры развернута и поверх нее созданы виртуальные машины, первое, что необходимо сделать — это развернуть ALD Pro и тем самым создать базовую программную инфраструктуру. 

Автоматизация развертывания ALD Pro с помощью API

Ручное развертывание всех компонентов ALD Pro в больших, распределенных инсталляциях — задача нетривиальная и затратная по времени. Именно здесь на помощь приходит автоматизация. Наличие документированного API позволяет  автоматизировано разворачивать систему ALD Pro. 

Начиная с версии 2.2.0, в ALD Pro появился полноценный API,  что стало поворотным моментом — особенно для нас, интеграторов. Описание API доступно в личном кабинете в виде YAML-файла в формате OpenAPI. Это сильно упростило автоматизацию, и теперь мы можем разворачивать ALD Pro не руками, а по нажатию кнопки. В K2Tex мы разработали и активно используем собственные сценарии для автоматизации развертывания подсистем ALD Pro.  

В качестве примера автоматизации процесса развертывания хочу с вами поделиться моими наработками на языке Bash. Развертывание первого контроллера представлено ниже (актуально для версии ALD Pro 2.5.0):

Скрытый текст
#!/bin/bash  
 
#************************************************************************************************************************** 
 
#Репозиторий Astra Linux - BASE 
ASTRA_BASE=" http://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.6/uu/2/repository-base/" 
#Репозиторий ALDPro 
ALD_MAIN="https://dl.astralinux.ru/aldpro/frozen/01/2.5.0/ " 
 
#Короткое имя контроллера домена 
DC_NAME="dc1" 
#IP-адрес контроллера домена 
IPADDR="192.168.8.10" 
#Маска сети 
MASK="255.255.255.0" 
#IP-адрес шлюза по-умолчанию 
GATEWAY="192.168.8.1" 
#Название домена 
DOMAIN_NAME="ald.pro.ru" 
#IP-адрес DNS серевера для КД на момент установки пакетов из внешнего сервера репозиториев, после заменится на 127.0.0.1 
NAMESERVER="77.88.8.8" 
#Ввод пароля учетной записи администратора домена 
echo "Укажите пароль для администратора домена:" 
read -sr PASSWORD_ADMIN 
#************************************************************************************************************************** 
 
function install_pdc() { 
 
 
 
#Добавление репозиториев Astra Linux 
cat <<EOL > /etc/apt/sources.list 
deb $ASTRA_BASE 1.7_x86-64 main non-free contrib 
EOL 
 
#Обновление ОС до актуальной версии 
apt update -y 
apt install astra-update -y && sudo astra-update -A -r -T 
 
#Добавление репозиториев ALD Pro 
cat <<EOL > /etc/apt/sources.list.d/aldpro.list 
deb $ALD_MAIN  1.7_x86-64  base  main 
EOL 
 
apt update -y 
 
#Изменение имени контроллера домена 
hostnamectl set-hostname $DC_NAME.$DOMAIN_NAME 
 
#Настройка сети 
cat <<EOL > /etc/network/interfaces 
source /etc/network/interfaces.d/* 
 
auto lo 
iface lo inet loopback 
 
auto eth0 
iface eth0 inet static 
address $IPADDR 
netmask $MASK 
gateway $GATEWAY 
dns-search $DOMAIN_NAME 
EOL 
 
#Настройка файла /etc/hosts 
cat <<EOL > /etc/hosts 
127.0.0.1 localhost.localdomain localhost 
$IPADDR $DC_NAME.$DOMAIN_NAME $DC_NAME  
EOL 
 
#Настройка файла /etc/resolv.conf 
cat <<EOL > /etc/resolv.conf 
search $DOMAIN_NAME 
nameserver $NAMESERVER 
EOL 
 
#Отключение службы NetworkManager 
systemctl stop NetworkManager 
systemctl disable NetworkManager 
systemctl enable networking 
systemctl restart networking 
 
 
#Настройка уровня безопасности 
LEVEL=`astra-modeswitch get` 
 
case $LEVEL in 
0|1) 
 
        astra-modeswitch set 2 
        astra-mic-control enable 
        astra-mac-control enable 
        echo "Уровень безопасности: Смоленск" 
        ;; 
 
2) 
        echo "Уровень безопасности: Смоленск" 
        ;; 
esac 
 
#Установка пакетов 
DEBIAN_FRONTEND=noninteractive apt-get install -q -y aldpro-mp aldpro-syncer aldpro-gc 
 
#Проверка установки пакета aldpro-mp 
ALDPRO_CHECK=`dpkg -l | grep aldpro-mp` 
if [[ -z $ALDPRO_CHECK ]]; 
then 
echo "Ошибка ввода в домен! Пакет aldpro-mp - не установлен в системе." 
exit 1 
 
else 
 
#Настройка /etc/resolv.conf 
cat <<EOL > /etc/resolv.conf 
search $DOMAIN_NAME 
nameserver 127.0.0.1 
EOL 
 
systemctl restart networking 
 
#Запуск развертывания первого контроллера домена 
aldpro-server-install -d $DOMAIN_NAME  -p $PASSWORD_ADMIN -n $DC_NAME --ip $IPADDR  --setup_syncer --setup_gc --no-reboot 
 
fi 
reboot 
 
} 
 
 
if [[ -z $DC_NAME ||  -z $IPADDR  ||  -z $MASK  ||  -z $GATEWAY  ||  -z $NAMESERVER  ||  -z $DOMAIN_NAME  ||  -z $PASSWORD_ADMIN ]]; 
then 
 
echo "Ошибка запуска скрипта! Проверьте заполняемые параметры и повторите попытку запуска." 
 
else 
 
install_pdc 
 
fi

Скрипт для ввода сервера в домен и развертывания подсистем представлен ниже (актуально для версии ALD Pro 2.5.0): 

Скрытый текст
#!/bin/bash  
 
#************************************************************************************************************************** 
 
#Репозиторий Astra Linux - BASE 
ASTRA_BASE="http://dl.astralinux.ru/astra/frozen/1.7_x86-64/1.7.6/uu/2/repository-base/" 
#Репозиторий ALDPro 
ALD_MAIN="https://dl.astralinux.ru/aldpro/frozen/01/2.5.0/ " 
 
#Короткое имя сервера 
SERVER_NAME="dc2" 
#IP-адрес сервера 
IPADDR="192.168.8.11" 
#Маска сети 
MASK="255.255.255.0" 
#IP-адрес шлюза по-умолчанию 
GATEWAY="192.168.8.1" 
#Название домена 
DOMAIN_NAME="ald.pro.ru" 
#IP-адрес первого контроллера домена 
NAMESERVER="192.168.8.10" 
FIRST_DC_NAME="dc1" 
SITENAME="Головной офис" 
#Ввод имя учетной записи с правами ввода в домен 
echo "Укажите учетную запись с правами ввода в домен:" 
read  ADMIN_NAME 
#Ввод пароля учетной записи с правами ввода в домен 
echo "Укажите пароль учетной записи для ввода в домен:" 
read -sr PASSWORD_ADMIN 
 
#************************************************************************************************************************** 
 
function join_domain() { 
 
#Изменение имени сервера 
hostnamectl set-hostname $SERVER_NAME.$DOMAIN_NAME  
 
 
#Настройка сети 
systemctl stop NetworkManager 
systemctl disable NetworkManager 
systemctl enable networking 
 
cat <<EOL > /etc/network/interfaces 
source /etc/network/interfaces.d/* 
 
auto lo 
iface lo inet loopback 
 
auto eth0 
iface eth0 inet static 
address $IPADDR 
netmask $MASK 
gateway $GATEWAY 
dns-nameservers $NAMESERVER 
dns-search $DOMAIN_NAME  
EOL 
 
#Настройка /etc/hosts 
cat <<EOL > /etc/hosts 
127.0.0.1 localhost.localdomain localhost 
$IPADDR $SERVER_NAME.$DOMAIN_NAME  $SERVER_NAME 
EOL 
 
#Настройка /etc/resolv.conf 
cat <<EOL > /etc/resolv.conf 
search $DOMAIN_NAME 
nameserver $NAMESERVER 
EOL 
 
#Добавление репозиториев Astra Linux 
cat <<EOL > /etc/apt/sources.list 
deb $ASTRA_BASE 1.7_x86-64 main non-free contrib 
EOL 
 
#Обновление ОС 
apt update -y 
apt install astra-update -y && sudo astra-update -A -r -T 
 
#Добавление репозиториев ALD Pro 
cat <<EOL > /etc/apt/sources.list.d/aldpro.list 
deb $ALD_MAIN 1.7_x86-64  base  main 
EOL 
 
systemctl restart networking 
 
#Настройка уровня безопасности 
LEVEL=`astra-modeswitch get` 
 
case $LEVEL in 
0|1) 
 
        astra-modeswitch set 2 
        astra-mic-control enable 
        astra-mac-control enable 
        echo "Уровень безопасности: Смоленск" 
        ;; 
 
2) 
        echo "Уровень безопасности: Смоленск" 
        ;; 
esac 
 
#Установка пакетов ALD Pro 
apt update -y 
DEBIAN_FRONTEND=noninteractive apt-get install -q -y aldpro-client 
ALDPRO_CHECK=`dpkg -l | grep aldpro-client` 
 
if [[ -z $ALDPRO_CHECK ]]; 
then 
echo "Ошибка ввода в домен! Пакет aldpro-client - не установлен в системе." 
exit 1 
 
else 
 
#Ввод в домен 
/opt/rbta/aldpro/client/bin/aldpro-client-installer -c $DOMAIN_NAME  -u $ADMIN_NAME  -p $PASSWORD_ADMIN -d $SERVER_NAME -i -f 
 
sleep 10 
systemctl stop sssd  
sed -i '/id_provider = ipa/a dyndns_update = true\ndyndns_refresh_interval = 60' /etc/sssd/sssd.conf 
systemctl restart sssd  
 
 
fi 
 
 
} 
 
function install_roles() { 
    echo "Укажите какую роль для сервера необходимо устанавливать? (DC, DHCP, REPO, CUPS, FS, MON, LOG, PXE):" 
    read  ROLE_NAME 
    echo "Укажите учетную запись с правами на установку подсистемы $ROLE_NAME:" 
    read  ROLE_USERNAME 
    echo "Укажите пароль учетной записи с правами на установку подсистемы $ROLE_NAME:" 
    read -sr ROLE_PASSWORD 
     curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/login" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{  \"data\": { \"login\": \"$ROLE_USERNAME\", \"password\": \"$ROLE_PASSWORD\" } }" \ 
          -c curl.cookie  
     
    case $ROLE_NAME in 
    DC) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME./ad/api/ds/domain-controllers" \ 
             -H 'accept: application/json' \ 
             -H 'Content-Type: application/json' \ 
             -d "{\"data\": { \"domaincontroller_ipa_login\": \"admin\", 
                            \"domaincontroller_ipa_password\": \"QAZxsw123\", 
                            \"domaincontroller_name\": \"$SERVER_NAME.$DOMAIN_NAME\", 
                            \"domaincontroller_roles\": [], 
                            \"domaincontroller_site_name\": \"$SITENAME\" } }" \ 
             -b curl.cookie 
        ;; 
 
    DHCP) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/dhcp/servers" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{\"data\": { \"dhcpserver_name\": \"$SERVER_NAME.$DOMAIN_NAME\", \"dhcpserver_site_name\": \"$SITENAME\" } }" \ 
          -b curl.cookie 
        ;; 
 
    REPO) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/repo/servers" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{\"data\": { \"repositoryserver_name\": \"$SERVER_NAME.$DOMAIN_NAME\", \"repositoryserver_site\": \"$SITENAME\" } }" \ 
          -b curl.cookie 
        ;; 
 
    CUPS) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/automation-tasks/print_install/run" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{ 
                \"data\": { 
                \"automationtask_computer_groups\": [ 
                  { 
                    \"automationtask_computer_group_name\": \"\" 
                  } 
                ], 
                \"automationtask_computers\": [ 
                  { 
                    \"automationtask_computer_name\": \"$SERVER_NAME.$DOMAIN_NAME\" 
                  } 
                ], 
                \"automationtask_organizational_units\": [ 
                  { 
                    \"automationtask_organizational_unit_name\": \"\" 
                  } 
                ], 
                \"automationtask_parameters\": [ 
                  { 
                    \"automationtask_attribute_cn\": \"sitename\", 
                    \"automationtask_attribute_value\": \"$SITENAME\" 
                  } 
                ] 
                } 
              }" \ 
          -b curl.cookie 
        ;; 
 
    FS) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/automation-tasks/samba_install/run" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{ 
                \"data\": { 
                \"automationtask_computer_groups\": [ 
                  { 
                    \"automationtask_computer_group_name\": \"\" 
                  } 
                ], 
                \"automationtask_computers\": [ 
                  { 
                    \"automationtask_computer_name\": \"$SERVER_NAME.$DOMAIN_NAME\" 
                  } 
                ], 
                \"automationtask_organizational_units\": [ 
                  { 
                    \"automationtask_organizational_unit_name\": \"\" 
                  } 
                ], 
                \"automationtask_parameters\": [ 
                  { 
                    \"automationtask_attribute_cn\": \"sitename\", 
                    \"automationtask_attribute_value\": \"$SITENAME\" 
                  } 
                ] 
                } 
              }" \ 
          -b curl.cookie 
        ;; 
 
    MON) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/automation-tasks/grafana_zabbix_install/run" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{ 
                \"data\": { 
                \"automationtask_computer_groups\": [ 
                  { 
                    \"automationtask_computer_group_name\": \"\" 
                  } 
                ], 
                \"automationtask_computers\": [ 
                  { 
                    \"automationtask_computer_name\": \"$SERVER_NAME.$DOMAIN_NAME\" 
                  } 
                ], 
                \"automationtask_organizational_units\": [ 
                  { 
                    \"automationtask_organizational_unit_name\": \"\" 
                  } 
                ], 
                \"automationtask_parameters\": [ 
                  { 
                    \"automationtask_attribute_cn\": \"sitename\", 
                    \"automationtask_attribute_value\": \"$SITENAME\" 
                  }, 
                  { 
                    \"automationtask_attribute_cn\": \"servicerole\", 
                    \"automationtask_attribute_value\": \"master\" 
                  } 
                ] 
                } 
              }" \ 
          -b curl.cookie 
        ;; 
    LOG) 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/automation-tasks/audit_install/run" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{ 
                \"data\": { 
                \"automationtask_computer_groups\": [ 
                  { 
                    \"automationtask_computer_group_name\": \"\" 
                  } 
                ], 
                \"automationtask_computers\": [ 
                  { 
                    \"automationtask_computer_name\": \"$SERVER_NAME.$DOMAIN_NAME\" 
                  } 
                ], 
                \"automationtask_organizational_units\": [ 
                  { 
                    \"automationtask_organizational_unit_name\": \"\" 
                  } 
                ], 
                \"automationtask_parameters\": [ 
                  { 
                    \"automationtask_attribute_cn\": \"sitename\", 
                    \"automationtask_attribute_value\": \"$SITENAME\" 
                  }, 
                  { 
                    \"automationtask_attribute_cn\": \"servicerole\", 
                    \"automationtask_attribute_value\": \"master\" 
                  } 
                ] 
                } 
              }" \ 
          -b curl.cookie 
        ;; 
 
    PXE) 
        echo "Укажите полное имя сервера DHCP для использование в связке с PXE. Для использования внешнего DHCP-сервера оставьте строку пустой" 
        read  DHCP_NAME 
        if [ -z "$DHCP_NAME" ]; then 
            DHCP_VALUE="true" 
        else 
            DHCP_VALUE="false" 
        fi 
        curl -X 'POST'  "https://$FIRST_DC_NAME.$DOMAIN_NAME/ad/api/ds/installer-servers" \ 
          -H 'accept: application/json'  \ 
          -H 'Content-Type: application/json' \ 
          -d "{ 
                \"data\": {  
                    \"installerserver_dhcpserver\": \"$DHCP_VALUE\",  
                    \"installerserver_dhcpserver_name\": \"$DHCP_NAME\", 
                    \"installerserver_ip_address\": \"\", 
                    \"installerserver_name\": \"$SERVER_NAME.$DOMAIN_NAME\",  
                    \"installerserver_site_name\": \"$SITENAME\" 
                } 
              }" \ 
          -b curl.cookie 
        ;; 
   
    esac 
 
} 
 
if [[ -z $SERVER_NAME  ||  -z $IPADDR  ||  -z $MASK  ||  -z $GATEWAY  ||  -z $NAMESERVER  ||  -z $DOMAIN_NAME  ||  -z $PASSWORD_ADMIN  ||  -z $ADMIN_NAME ]]; 
then 
 
echo "Ошибка запуска скрипта! Проверьте заполняемые параметры и повторите попытку запуска." 
 
else 
 
join_domain 
install_roles 
 
fi 

Язык Bash удобен для написания простых автоматизаций и позволяет создавать конвейеры для последовательной обработки информации. Но в тех случаях, когда необходимо исполнить параллельно несколько задач, и перед нами стоят вопросы консистентности, идемпотентности, предпочтительнее использовать декларативный подход систем управления конфигурациями. Например, сейчас я переношу все свои наработки на Ansible и  поделюсь с вами наиболее полезными наработками, а именно обращениями к API  ALD Pro для автоматизированного развертывания подсистем. 

Пример обращения для авторизации в API: 

- name: ALD api login 
  ansible.builtin.uri:  
    validate_certs: true 
    url: "https://{{ dc01_fqdn }}/ad/api/ds/login" 
    method: POST 
    headers: 
      accept: application/json 
      Content-Type: application/json 
    body_format: json 
    body:  
      data:  
        login: "{{ domain_admin }}" 
        password: "{{ domain_password }}" 
    return_content: true 
  delegate_to: "{{ dc01_fqdn }}" 

Примеры обращений к API для автоматизированного развертывания подсистем:

Скрытый текст
 ald_api: 
  dc: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/domain-controllers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/domain-controllers 
    body: 
      data: 
        domaincontroller_ipa_login: "{{ domain_admin }}" 
        domaincontroller_ipa_password: "{{ domain_password }}" 
        domaincontroller_name: "{{ ansible_nodename }}" 
        domaincontroller_roles: [] 
        domaincontroller_site_name: "{{ ald_site_name }}" 
  fs: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/shared-folders/servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/automation-tasks/samba_install/run 
    body: 
      data: 
        automationtask_computer_groups: 
          - automationtask_computer_group_name: '' 
        automationtask_computers: 
          - automationtask_computer_name: "{{ ansible_nodename }}" 
        automationtask_organizational_units: 
          - automationtask_organizational_unit_name: '' 
        automationtask_parameters: 
          - automationtask_attribute_cn: sitename 
            automationtask_attribute_value: "{{ ald_site_name }}" 
  audit: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/logging/servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/automation-tasks/audit_install/run 
    body: 
      data: 
        automationtask_computer_groups: 
          - automationtask_computer_group_name: '' 
        automationtask_computers: 
          - automationtask_computer_name: "{{ ansible_nodename }}" 
        automationtask_organizational_units: 
          - automationtask_organizational_unit_name: '' 
        automationtask_parameters: 
          - automationtask_attribute_cn: sitename 
            automationtask_attribute_value: "{{ ald_site_name }}" 
          - automationtask_attribute_cn: servicerole 
            automationtask_attribute_value: master 
  dhcp: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/dhcp/servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/dhcp/servers 
    body: 
      data: 
        dhcpserver_name: "{{ ansible_nodename }}" 
        dhcpserver_site_name: "{{ ald_site_name }}" 
  ps: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/print-service/servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/automation-tasks/print_install/run 
    body: 
      data: 
        automationtask_computer_groups: 
          - automationtask_computer_group_name: '' 
        automationtask_computers: 
          - automationtask_computer_name: "{{ ansible_nodename }}" 
        automationtask_organizational_units: 
          - automationtask_organizational_unit_name: '' 
        automationtask_parameters: 
          - automationtask_attribute_cn: sitename 
            automationtask_attribute_value: "{{ ald_site_name }}" 
  repo: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/repo/servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/repo/servers 
    body: 
      data: 
        repositoryserver_name: "{{ ansible_nodename }}" 
        repositoryserver_site: "{{ ald_site_name }}" 
  mon: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/monitoring/servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/automation-tasks/grafana_zabbix_install/run 
    body: 
      data: 
        automationtask_computer_groups: 
          - automationtask_computer_group_name: '' 
        automationtask_computers: 
          - automationtask_computer_name: "{{ ansible_nodename }}" 
        automationtask_organizational_units: 
          - automationtask_organizational_unit_name: '' 
        automationtask_parameters: 
          - automationtask_attribute_cn: sitename 
            automationtask_attribute_value: "{{ ald_site_name }}" 
          - automationtask_attribute_cn: servicerole 
            automationtask_attribute_value: "master" 
  pxe: 
    get_url: https://{{ dc01_fqdn }}/ad/api/ds/installer-servers 
    post_url: https://{{ dc01_fqdn }}/ad/api/ds/installer-servers 
    body: 
      data: 
        installerserver_dhcpserver: 'false' 
        installerserver_dhcpserver_name: '{{ dhcp_server_fqdn }}' 
        installerserver_ip_address: '' 
        installerserver_name: "{{ ansible_nodename }}" 
        installerserver_site_name: "{{ ald_site_name }}"

Инструменты миграции с Active Directory на ALD Pro

Успешное развертывание ALD Pro — это только полдела. Для многих компаний, особенно в рамках стратегии импортозамещения, следующим шагом является миграция с существующей инфраструктуры, чаще всего построенной на базе Microsoft Active Directory. 

Для этого можно использовать следующие инструменты миграции: 

  • встроенный в ALD Pro инструмент миграции из MS AD (не рекомендуется для больших инфраструктур);  

  • встроенный в ALD Pro модуль синхронизации с MS AD; 

  • Pragmatic Tools Migrator; 

  • собственные наработки K2Tex. 

Разберем каждый инструмент из списка подробнее.

Встроенный в ALD Pro инструмент миграции из MS AD

Начиная с самой первой версии ALD Pro, в нем присутствует встроенный инструмент миграции с MS AD. В интерфейсе его можно найти по пути: «Управление доменом» -> «Интеграция с доменами» -> «Миграциия с MS AD»

Запуск миграции выглядит следующим образом: 

 

Он позволяет сопоставить подразделения доменов MS AD и ALD Pro, выбрать необходимые объекты для миграции (пользователей, группы, организационные подразделения) и сопоставить для объектов необходимые атрибуты для миграции. Инструмент предназначен для разового переноса объектов из домена MS AD в ALD Pro. Но в нём есть ограничения: 

  1. В ходе миграции объекты из MS AD переносятся в ALD Pro только один раз. Если после выполнения миграции какие-то атрибуты объекта в MS AD будут изменены, то при повторной миграции эти изменения не отразятся в ALD Pro. Для того, чтобы перенести измененный объект еще раз, вам потребуется его полностью удалить из ALD Pro. 

  2. Чтобы сохранить членство пользователей в группах, необходимо мигрировать сначала все группы из MS AD, и только затем мигрировать пользователей. 

  3. Нигде не отображаются статусы исполнения задач во время миграции (количество мигрированных объектов успешно/с ошибкой или в очереди). Только по факту завершения задачи можно ознакомиться с результатами ее выполнения. 

Этот инструмент очень прост в использовании, но подходит только для разовой миграции объектов с MS AD в небольших инфраструктурах до 2-3 тысяч объектов. Нам, конечно, известно об успешном применении этого модуля и на 20-30 тысяч, но в этом случае серверу нужно обязательно накидывать дополнительные ресурсы и очень терпеливо ждать полного завершения процесса.

Встроенный в ALD Pro модуль синхронизации с MS AD

Начиная с релиза 2.0.0, в ALD Pro добавлен специализированный инструмент, который закрыл указанные в предыдущем разделе недостатки. Модуль синхронизации устанавливается на первый контроллер домена при инсталляции или обновлении системы. В веб-интерфейсе первого контроллера домена он находится на главной странице:  

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

Дополнительно к этому модуль позволил синхронизировать пароли в обе стороны, но если вы не хотите ослаблять политику безопасности для MS AD, то эту функцию можно не использовать и задавать первоначальный пароль пользователя в веб-интерфейсе ALD Pro. 

Это большое преимущество, когда в службе каталогов есть встроенный модуль для миграции с MS AD, да еще и включающий синхронизацию паролей. Данный инструмент подойдет для более длительных процессов миграции благодаря возможности синхронизировать изменения, при этом администраторы смогут самостоятельно отслеживать перенос данных из MS AD в ALD Pro, хотя и не в графическом интерфейсе, а в подробном журнале событий (можно применить уровень логирования debug).

Pragmatic Tools Migrator

Решение Pragmatic Tools Migrator от компании Pragmatic Tools, специализирующейся на разработке инструментов миграции из MS AD в отечественные службы каталогов (не только ALD Pro), покрывает функциональность штатного модуля синхронизации, а также предоставляет ряд дополнительных возможностей, которые могут носить решающий характер в отдельных сценариях миграции: 

  • миграция паролей с MS AD в ALD Pro (не требует ослаблять политику безопасности в MS AD);

  • возможность настройки правил миграции (например, по подразделениям); 

  • возможность настройки правил трансформации (изменения) объектов при их синхронизации; 

  • возможность ручного запуска и многое другое. 

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

Собственные наработки K2Tex

Следующий инструмент — это наши собственные наработки в К2Тех, которые мы создаем в тех случаях, когда заказчик предъявляет нестандартные требования, не реализованные в описанных ранее инструментах. Например, у одного из заказчиков использовалась служба каталога на базе Novell eDirectory, для которой на рынке нет готовых инструментов миграции, поэтому для реализации миграции (синхронизации) объектов домена мы разработали собственные сценарии. 

Также мы очень часто сталкивались со специфичными атрибутами каталога, которые заполнялись с пробелами или содержали символы, недопустимые для атрибутов ALD Pro. Как раз под такие задачи мы разрабатывали сценарии с правилами транслитерации. 

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

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

Инструмент миграции

Когда использовать

Инструмент миграции из MS AD

Подходит для разовой миграции объектов с MS AD

Модуль синхронизации ALD Pro

Подходит для относительно небольших и простых инфраструктур

Pragmatic Tools Migrator

Решение, которое мы рекомендуем и используем для крупных и сложных миграций с сотнями тысяч объектов

Собственные наработки K2Tex

Для нестандартных инфраструктур заказчиков, сценариев средней сложности, но со специфическими требованиями заказчиков. 

Развертывание рабочих мест пользователей с ОС Astra Linux

Следующим этапом после миграции объектов службы каталога является развертывание рабочих мест пользователей с ОС Astra Linux. В рамках ALD Pro имеется подсистема для установки ОС по сети. В актуальной версии (на момент написания статьи — 2.5.0) возможна установка по сети ОС Astra Linux 1.7, но многие уже эксплуатируют версию 1.8. Как раз здесь хочется рассказать о возможности настройки развертывания версии 1.8 с установщиком Debian: 

  • Разархивировать ISO-образ версии 1.8. 

  • Скопировать файлы initrd и linux из ISO-образ версии 1.8 на сервер установки ОС по сети в директорию /var/www/tftp/. Обязательно переименовать данные файлы, так как на сервере с этими названиями уже присутствуют файлы для установки ОС Astra Linux 1.7 (Например: linux_18, 18_initrd.gz). 

  • Создать новый профиль загрузки в веб-интерфейсе ALD Pro (Например: Astra18).

  • В новом созданном профиле загрузки привести скрипт Grub к виду:

if loadfont $prefix/font.pf2 ; then 
  set gfxmode=800x600 
  set gfxpayload=keep 
  insmod efi_gop 
  insmod efi_uga 
  insmod video_bochs 
  insmod video_cirrus 
  insmod gfxterm 
  insmod png 
  terminal_output gfxterm 
fi 
  
if background_image /isolinux/aldpro.png; then 
  set color_normal=light-gray/black 
  set color_highlight=white/black 
else 
  set menu_color_normal=cyan/blue 
  set menu_color_highlight=white/blue 
fi 
# Установка Astra Linux Special Edition 
menuentry 'Install AstraLinux Operating System' {1 
    set background_color=black 
    linux    (http,<IP-адрес вашего сервера установки ОС по сети>)/tftp/18_linux modprobe.blacklist=evbug debian-installer/allow_unauthenticated=true auto=true priority=critical debian-installer/locale=en_US console-keymaps-at/keymap=ru hostname={HOSTNAME} domain={DOMAIN_REALM} astra-license/license=true url=http://<IP-адрес вашего сервера установки ОС по сети>/tftp/{PROFILE_UNIQ_NAME}/{PRESEED_FILE_NAME} interface=auto netcfg/dhcp_timeout=60   
initrd   (http,<IP-адрес вашего сервера установки ОС по сети>)/tftp/18_initrd.gz 
} 
  • В новом созданном профиле загрузки привести скрипт Boot-меню к виду:

UI vesamenu.c32 
# 0 - отключение приглашения командной строки. Загрузка начнется сразу 
PROMPT 0 
# Автоматический выбор меню по таймеру. Секунды вычисляются делением на 10 введенного значения 
TIMEOUT 100 
# Выбор по-умолчанию загрузки по сети 
DEFAULT pxeinstall 
menu background back.jpg 
menu title User Management Portal Boot Menu 
# Локальная загрузка с диска 
LABEL bootlocal 
    menu label Boot from first HDD 
    kernel chain.c32 
    append hd0 0 
TEXT HELP 
Normal boot from HDD 
ENDTEXT 
# Загрузка из сети 
LABEL pxeinstall 
    menu label PXE operation system install 
    kernel linux 
   append initrd=18_initrd.gz vga=788 auto=true priority=critical debian-installer/locale=ru_RU console-keymaps-at/keymap=ru hostname={HOSTNAME} domain={DOMAIN_REALM} astra-license/license=true url=http://<IP-адрес вашего сервера установки ОС по сети>/tftp/{PROFILE_UNIQ_NAME}/{PRESEED_FILE_NAME} interface=auto netcfg/dhcp_timeout=60 
TEXT HELP 
Install AstraLinux Operating System 
ENDTEXT
  • В новом созданном профиле загрузки в preseed файле, привести настройку сетевого интерфейса к виду:

# Автоматический выбор сетевого интерфейса
d-i netcfg/choose_interface select enp4s0
  • ALD Pro не предполагает настроек для установки разных версий операционных систем из веб-интерфейса и для отображения необходимой операционной системы нужно в базу данных PostgreSQL добавить строки с версией ОС  на сервере PXE, для этого выполнить следующие действия:

Запустить оболочку из-под пользователя postgres: 

sudo –i –u postgres 

Версии операционных систем хранятся в таблице installs_os. Для просмотра  текущих ОС необходимо выполнить команду: 

psql -d osinstall -c" select* from installs_os;” 

Изначально там будет присутствовать только запись с версией Astra_1_7. Чтобы добавить запись, например, с 1_8,  необходимо выполнить команду:

psql -d osinstall -c "INSERT INTO  installs_os  VALUES ('Astra_1_8', '/astra18/bin/1', '64', '/opt/rbta/aldpro/os/storage/astra17/bin/1/', '1', 'Головной офис');"

Но на этом еще не все. Теперь нужно обновить запись с используемой ОС в нужном нам профиле. Для просмотра  используемых профилей  необходимо выполнить команду:

psql -d osinstall -c "select* from installs_osprofile;” 

Для обновления записи в профиле необходимо выполнить команду: 

psql -d osinstall -c " UPDATE installs_osprofile SET os_id = 'Astra_1_8'  WHERE os_id = 'Astra_1_7';” 

Теперь можно устанавливать Astra Linux 1.8 посредством ALD Pro, в веб-интерфейсе будет отображаться указанная версия операционной системы:

Групповые политики и кастомизация

Групповые политики (GPO) — один из самых важных инструментов управления инфраструктурой. В ALD Pro этот механизм реализован на базе Salt, но со временем эволюционировал. Изначально использовалась классическая модель «мастер-миньон». Позже появилась автономная служба aldpro-salt-minion, которая позволяет рабочим станциям самостоятельно получать конфигурации и скрипты через LDAP, что повышает производительность и отказоустойчивость. 

Гибкость системы GPO в ALD Pro позволяет реализовывать кастомные дополнительные параметры под специфические требования заказчиков, например, для настройки корпоративного ПО. С помощью дополнительных параметров можно реализовать также и более сложную логику, которая выходит за рамки функциональных возможностей продукта. Например, можно сделать так, чтобы параметр применялся только для определенных групп пользователей или компьютеров. 

Шаблон групповой политики для применения на уровне групп пользователей:

{% set id = '<Уникальный идентификатор ГП>' %} 
{% set my_user = salt['pillar.get']("user") %} 
{% set homedir = salt['user.info'](my_user).home %} 
{% set apply_to_user_group_ids = (salt['pillar.get']('aldpro-users:'+ my_user + id)).split(' ') %} 
  
{% if apply_to_user_group_ids %} 
{% set groups_user = (salt['cmd.run']('id -G ' + my_user)).split(' ') %} 
{% for group_user in groups_user %} 
{% if group_user in apply_to_user_group_ids %} 
 
< Тело групповой политики> 
 
{% break %} 
{% endif %} 
{% endfor %} 
{% endif %} 

Шаблон  групповой политики для применения на уровне групп компьютеров:

{% set id = '<Уникальный идентификатор ГП>' %} 
{% set my_host = salt['grains.get']("nodename") %} 
{% set domain = salt['grains.get']("domain") %} 
{% if salt['pillar.get']('aldpro-hosts:'+ my_host) is defined %} 
{% set apply_to_pc_group_names = (salt['pillar.get']('aldpro-hosts:'+ my_host +':' + id)).split(' ')  %} 
 
{% if apply_to_pc_group_names %} 
{% for group_pc in apply_to_pc_group_names %} 
{% if (salt['cmd.run']("getent netgroup " + group_pc + " " + node + " " + "-" + " " + ))[-1] == '1' %} 
 
< Тело групповой политики> 
 
{% break %} 
{% endif %} 
{% endfor %} 
{% endif %}

Одним из примеров кастомного сценария  может быть автоматическое монтирование сетевых ресурсов с помощью механизма pam_mount при входе пользователя в систему. Несмотря на то, что правила автоматического монтирования autofs, которые можно централизованно настроить в домене FreeIPA, имеют целый ряд преимуществ, способ через pam_mount может быть все же востребован — например, если вам потребуется реализовать подключение пользовательского профиля с файлового сервера.

Используя Salt, который лежит в основе групповых политик ALD Pro, можно управлять конфигурационными файлами, такими как pam_mount.conf.xml, для динамического подключения CIFS/SMB-ресурсов на основе данных пользователя из LDAP. Ниже приведен пример фрагмента SLS-файла для добавления правил монтирования. 

Директория монтирования на клиенте

mountpoint

Целевая директория, куда будет смонтирована сетевая папка на клиентском АРМ, например: /home/%(USER)/Desktops/Desktop1/

Название сетевой папки на файловом сервере

path

Название сетевой папки на файловом сервере

Название файлового сервера

server

В формате FQDN файлового сервера

Локальные права на директорию

share_local_permissions

Права на примонтируемую директорию на клиенте, например: 0777

Скрытый текст
# Пример управления pam_mount.conf.xml через Salt (управляется через ALD Pro GPO) 
# Задаем переменные (путь к конфигу, данные о шарах - могут приходить из Pillar/LDAP) 
{% set config_f = '/etc/security/pam_mount.conf.xml' %} 
{% set get_vars = [ 
    {'server': 'fileserver.domain.local', 'path': 'share1', 'mountpoint': '/mnt/share1', 'share_local_permissions': '0770'}, 
    {'server': 'otherserver.domain.local', 'path': 'docs', 'mountpoint': '~/docs', 'share_local_permissions': '0700'} 
  ] 
%} 
 
# Убеждаемся, что базовый файл существует и имеет корректный заголовок 
pam_mount_conf_xml_header: 
  file.managed: 
    - name: {{ config_f }} 
    - contents: | 
        <?xml version="1.0" encoding="utf-8" ?> 
        <!DOCTYPE pam_mount SYSTEM "pam_mount.conf.xml.dtd"> 
        <pam_mount> 
            <debug enable="0" /> 
            <cifsmount>mount.cifs //%(SERVER)/%(VOLUME) %(MNTPT) -o %(OPTIONS) </cifsmount> 
 
# Добавляем блоки <volume> для каждой шары 
{% for item in get_vars %} 
{% set server_s = item.get('server') %}\ 
{% set path_s = item.get('path') %}\ 
{% set mountpoint_s = item.get('mountpoint') %}\ 
{% set share_local_permissions_s = item.get('share_local_permissions') %} 
pam_mount_conf_xml_addvol_{{ path_s }}: 
  file.append: 
    - name: {{ config_f }} 
    - text: | 
        <volume 
                fstype="cifs" 
                server="{{ server_s }}" 
                path="{{ path_s }}" 
                mountpoint="{{ mountpoint_s }}" 
                options="user=%(USER),cruid=%(USER),sec=krb5i,file_mode={{ share_local_permissions_s }},dir_mode={{ share_local_permissions_s }}" 
            /> 
{% endfor %} 
 
# Добавляем завершающие теги 
pam_mount_conf_xml_footer: 
  file.append: 
    - name: {{ config_f }} 
    - text: | 
        <luserconf name=".pam_mount.conf.xml" /> 
            <mntoptions allow="nosuid,nodev,loop,encryption,sec=krb5i" /> 
            <logout wait="0" hup="0" term="0" kill="0" /> 
            <mkmountpoint enable="1" remove="true" /> 
        </pam_mount> 

 Приведенные примеры демонстрируют способы управления конфигурацией средствами Salt. В рамках ALD Pro такие задачи могут быть реализованы через кастомные сценарии групповых политик, что позволяет централизованно настраивать окружение пользователей и конфигурацию компьютеров.

Безопасность: PKI Proxy и защита ключей

PKI Proxy — это новый модуль ALD Pro, который обеспечивает выпуск и управление сертификатами безопасности в рамках инфраструктуры ALD Pro. В некоторых сценариях возможно разворачивать более одного инстанса для отказоустойчивости. Доменные компьютеры могут отправить запрос на получение сертификата через LDAP, а контроллер домена с ролью PKI Proxy может получить этот запрос, обработать его и также через LDAP передать компьютеру готовый сертификат. Такой подход исключает необходимость хранения закрытых ключей на всех репликах домена, что существенно повышает безопасность инфраструктуры. 

При развертывании реплик контроллеров домена можно выбрать данную роль: 

Вендор активно развивает этот модуль. Планируется расширение интеграции с внешними Удостоверяющими Центрами, в том числе российскими, такими как CryptoPro УЦ и SafeTech CA. Это позволит использовать сертификаты, соответствующие российским криптографическим стандартам (ГОСТ), что важно для выполнения требований регуляторов и работы с защищенными ресурсами. 

Вот простой пример того, как работает PKI Proxy: есть контроллер домена с ролью PKI Proxy КД-1, его реплика КД-2 без роли PKI Proxy, а также есть вновь добавляемый в домен контроллер КД-3. На сетевом уровне КД-3 не имеет доступа к КД-1, но имеет к КД-2.

При добавлении в домен КД-3 происходит следующее:

  1. Процесс развертывания КД-3 добавляет запрос на получение сертификата в учетную запись КД-3 в каталоге на КД-2. 

  2. КД-2 реплицирует изменения на КД-1.

  3. КД-3 ожидает в течение 5 минут появления сертификата в соответствующем атрибуте своей УЗ.

  4. PKI Proxy на КД-1 видит запрос на получение сертификата, выпускает его, записывает сертификат и удаляет запрос в УЗ КД-3 в своем каталоге. 

  5. КД-1 реплицирует изменения на КД-2.

  6. КД-3 видит свой сертификат на КД-2, напрямую не связываясь с КД-1.

  7. Если КД-3 через 5 минут не увидит свой сертификат, он сделает еще 6 попыток с интервалами в 5 минут. Такое поведение обусловлено тем, что репликация может занимать продолжительное время, особенно если в LDAP активно вносятся изменения или PKI Proxy в топологии домена сильно удален от клиента, который запрашивает сертификат.

  8. После получения сертификата процесс добавления контроллера домена КД-3 продолжается и на нем создается реплика каталога.

Эволюция процесса обновлений ALD Pro

Продукт ALD Pro активно развивается. Периодически необходимо устанавливать новые версии, при этом процесс обновления может быть не всегда простым (в зависимости от количества новых функций и изменений в очередном релизе). 

С 2.4.0 обновления ALD Pro стали выполняться по кнопке в веб-интерфейсе. Администратор назначает политику обновления ALD Pro, клиенты сами скачивают нужные скрипты из LDAP и применяют обновления. Процесс обновления ALD Pro постоянно совершенствуется вендором для повышения удобства и надежности, особенно в крупных инфраструктурах: 

  • До версии 2.2.0: Обновление запускалось одной командой, которая централизованно обновляла все компоненты (серверы и клиенты), что могло создавать высокую нагрузку. 

  • С версии 2.2.0: Процесс разделили. Ключевые компоненты (первый КД, подсистемы) обновлялись централизованной командой, а клиенты получали обновления через специальную встроенную групповую политику. 

  • С версии 2.4.0: Обновление первого КД выполняется набором команд на нем самом, подсистемы можно обновить через веб-интерфейс (нажатием кнопки), а клиенты теперь переходят на новую версию самостоятельно через политику обновления ALD Pro, считывая необходимые скрипты и инструкции напрямую из LDAP-каталога. Это снимает зависимость от механизма Salt для доставки обновлений клиентам и повышает скорость и надёжность процесса в больших и географически распределенных сетях. 

Также мы реализовали два сценария обновления ОС:

  • Bash-скрипт с уведомлениями пользователей;

  • Python-скрипт с дополнительными функциями:

    • валидацией репозиториев ОС; 

    • резервным копированием (/opt/backup -> файловый сервер); 

    • логированием и отправкой отчёта на почту;

Перед использованием скрипта обновления ОС посредством Bash  необходимо включить оповещения в ОС для всех пользователей. Затем необходимо сделать перезагрузку ПК, поэтому можно заготовить заранее ГП или даже добавить в заливку ОС по сети сценарий: 

#!/bin/bash 
####Включение оповещений для всех пользователей системы 
sudo apt install libdbus-glib-1* fly-notifications -y 
  
cat <<EOL > /etc/xdg/fly-notificationsrc 
[Notifications] 
ListenForBroadcasts=true 
EOL

Сценарий на BASH протестирован при переходе с Astra 1.8.1 UU1 на  1.8.1 UU2 :

Скрытый текст
#!/bin/bash 
#************************************************************************************************************************** 
ASTRA_BASE="https://dl.astralinux.ru/astra/frozen/1.8_x86-64/1.8.1/uu/2/main-repository/" 
ASTRA_EXT="https://dl.astralinux.ru/astra/frozen/1.8_x86-64/1.8.1/uu/2/extended-repository/" 
ASTRA_VERSION_DST="1.8.1" 
ASTRA_BUILD_VERSION_DST="1.8.1.16" 
ASTRA_VERSION_SRC=`cat /etc/astra_version | head -n 1| awk -F" " '{print $1}'` 
ASTRA_BUILD_VERSION_SRC=`cat /etc/astra/build_version | head -n 1 | awk -F" " '{print $1}'` 
NOTIFICATION_USER="Добрый день! Ваш компьютер будет обновлен до новой версии ОС Astra Linux через 10 минут.\nПросим вас пожалуйста закрыть критическое ПО для предотвращения проблем с его данными." 
NOTIFICATION_USER_POST="Добрый день! На вашем компьютере обновляется операционная система! Просьба не выключать ваш компьютер и не редактировать критические файлы!" 
NOTIFICATION_USER_POST_UPGRADE="Добрый день! Ваша ОС успешно обновлена, вы можете продолжить работу. Хорошего вам дня!" 
#************************************************************************************************************************** 
 
 
if [[ ("$ASTRA_VERSION_DST" != "$ASTRA_VERSION_SRC") || ("$ASTRA_BUILD_VERSION_DST" != "$ASTRA_BUILD_VERSION_SRC") ]]; then 
 
#Добавление репозиториев Astra Linux 
cat << EOL > /etc/apt/sources.list 
deb $ASTRA_BASE 1.8_x86-64 contrib main non-free non-free-firmware 
EOL 
 
  #Обновление репозиториев ОС 
  apt update -y 
  apt install astra-update -y 
 
  #Оповещение пользователей об обновлении ОС. 
  gdbus emit --system --object-path / --signal org.kde.BroadcastNotifications.Notify "{'appIcon': <'network-disconnect'>, 'body': <'$NOTIFICATION_USER'>, 'summary': <'Обновление ОС.'>, 'timeout': <'600000'>}" 
  sleep 600 
 
  gdbus emit --system --object-path / --signal org.kde.BroadcastNotifications.Notify "{'appIcon': <'network-disconnect'>, 'body': <'$NOTIFICATION_USER_POST'>, 'summary': <'Обновление ОС.'>, 'timeout': <'60000'>}" 
  #Обновление ОС 
  astra-update -A -r -T 
 
  ASTRA_BUILD_VERSION_SRC_POST=`cat /etc/astra/build_version | head -n 1 | awk -F" " '{print $1}'` 
  echo $ASTRA_BUILD_VERSION_SRC_POST 
 
#Добавление репозиториев Astra Linux, включая расширенный 
cat << EOL > /etc/apt/sources.list 
deb $ASTRA_BASE 1.8_x86-64 contrib main non-free non-free-firmware 
deb $ASTRA_EXT 1.8_x86-64 contrib main non-free non-free-firmware 
EOL 
 
 
  #Обновление репозиториев ПО 
  apt update -y 
  gdbus emit --system --object-path / --signal org.kde.BroadcastNotifications.Notify "{'appIcon': <'network-disconnect'>, 'body': <'$NOTIFICATION_USER_POST_UPGRADE'>, 'summary': <'Обновление ОС.'>, 'timeout': <'60000'>}" 
 
 
  if [[ "$ASTRA_BUILD_VERSION_DST" != "$ASTRA_BUILD_VERSION_SRC_POST" ]]; then 
  echo "Ошибка обновления! Проверьте логи обновления на наличие ошибок: /var/log/astra_update*.log" 
  gdbus emit --system --object-path / --signal org.kde.BroadcastNotifications.Notify "{'appIcon': <'network-disconnect'>, 'body': <'Ошибка обновления ОС - обратитесь к администратору!'>, 'summary': <'Обновление ОС.'>, 'timeout': <'60000'>}" 
  exit 1 
  fi 
 
else 
 
  echo -e "ОС уже была обновлена.\nАктуальная версия: $ASTRA_VERSION_SRC\nАктуальный build: $ASTRA_BUILD_VERSION_SRC" 
  exit 0 
 
fi 

Сценарий на языке Python протестирован при переходе с Astra 1.7.5 на 1.7.6. 

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

Переменная окружения

Назначение

Формат

MAIL_USER

Почтовый адрес отправителя

user@domain.ald.pro

MAIL_PASS

Пароль почтового адреса отправителя

FS_USER

Пользователь с доступом на запись на файловом сервере

user

FS_PASSWORD

Пароль пользователя на файловом сервере

Начнем с блока со всеми библиотеками, статическими переменными и маленькими функциями:

Скрытый текст
import os 
import subprocess 
import smtplib 
import ssl 
from email.mime.text import MIMEText 
from email.mime.image import MIMEImage 
from email.mime.multipart import MIMEMultipart 
from email.mime.base import MIMEBase 
from email import encoders 
import logging 
 
DEFINE_COMMAND_GET_VERSION_ALDPRO_CLIENT = "dpkg -l | grep -i aldpro-client | head -n 1 | awk -F' ' '{print $3}' | grep 2.4.0" 
DEFINE_LOG_FILE_176 = "/var/log/upgrade_to_176.log" 
DEFINE_REPO_ASTRA_EXT = "deb https://vm-repo-01.domain.ald.pro/repos/astra17/astra-176-ext/ 1.7_x86-64 main non-free contrib" 
DEFINE_REPO_ASTRA_BASE = "deb https://vm-repo-01.domain.ald.pro/repos/astra17/astra-176-base/ 1.7_x86-64 main non-free contrib" 
DEFINE_REPO_SERVER_VALIDATE = "vm-repo-01.domain.ald.pro" 
DEFINE_CRED_EMAIL = { 
    "user": os.environ.get('MAIL_USER'), 
    "password": os.environ.get('MAIL_PASS'), 
    "smtp_server": "mail.domain.ald.pro", 
    "smtp_port": 465, 
    "smtp_ssl": "True" 
} 
DEFINE_CRED_FS = { 
    "user": os.environ.get('FS_USER'), 
    "password": os.environ.get('FS_PASS'), 
    "server_fqdn": vm-fs-01.domain.ald.pro 
} 
def get_os_version(): 
    """ 
    :return: Возвращает версию ОС Astra Linux 
    """ 
    with open("/etc/astra_version") as file: 
        astra_version = file.readline().rstrip('\n') 
        return str(astra_version) 
 
def get_kernel_version(): 
    """ 
    :return: Возвращает версию ядра ОС 
    """ 
    return str(os.uname()[2]) 
 
 
def get_fqdn(): 
    """ 
    :return: Возвращает FQDN АРМ 
    """ 
    return str(os.uname()[1]) 
 
 
def get_version_aldpro_client(): 
    """ 
    :return: Возвращает версию ALD PRO клиента 
    """ 
    result = subprocess.run(DEFINE_COMMAND_GET_VERSION_ALDPRO_CLIENT, capture_output=True, shell=True).stdout.decode() 
    return result.rstrip('\n') 

Все библиотеки уже установлены на АРМ, введенный в домен ALD PRO 2.4.0, поэтому нет необходимости в их доустановке. Далее рассмотрим блок с функциями валидации и резервного копирования данных:

Скрытый текст
def validate_repos_and_backup(logger: logging): 
    """ 
    :logger: Объект класса для логирования операций 
    :return: Возвращает bool-значение в случае успеха/неудачи 
    """ 
    directory = "/etc/apt/sources.list.d/" 
    command_update = "apt update -y" 
    command_mount = f"mount -t cifs //{DEFINE_CRED_FS["server_fqdn"]}/backup/ /mnt -o username={ DEFINE_CRED_FS["user"]},password=''{DEFINE_CRED_FS["password"]}" 
    command_umount = "umount /mnt -f -l" 
    command_backup_folder = f"cp -r -v /opt/backup/ /mnt/root_backup/{get_fqdn()}" 
    list_repos = [] 
    list_repos += os.listdir(directory) 
    for repolist in list_repos: 
        with open(directory + str(repolist), 'r') as file: 
            lines = file.readlines() 
        with open(directory + str(repolist), 'w') as file: 
            for line in lines: 
                if line.strip() and line.find("#") and line.rfind(DEFINE_REPO_SERVER_VALIDATE) != -1: 
                    file.write(line) 
 
    try: 
        result_update = subprocess.run(command_update, capture_output=True, shell=True) 
        logger.info(":\n" + result_update.stdout.decode()) 
        logger.info(":\n" + "Валидация репозиториев закончена.\nВыполняется резервное копирование папки /opt/backup.") 
    except subprocess.CalledProcessError as error_process: 
        logger.info(f":\n" + f"Ошибка выполнения обновления репозиториев и команды apt update.\nПодробнее: " 
                             f"{error_process}") 
        return False 
 
    try: 
        subprocess.run(command_mount, capture_output=True, shell=True) 
    except subprocess.CalledProcessError as error_process: 
        logger.error(":\n", error_process) 
 
    if os.path.exists("/mnt/root_backup"): 
        os.mkdir(f"/mnt/root_backup/{get_fqdn()}") 
    else: 
        logger.error(":\n" + "Ошибка монтирования сетевой директории для бэкпаирования!") 
        return False 
 
    try: 
        result_cp = subprocess.run(command_backup_folder, capture_output=True, shell=True) 
        logger.info(":\n" + result_cp.stdout.decode()) 
    except subprocess.CalledProcessError as error_process: 
        logger.error(":\n", error_process) 
        return False 
    subprocess.run(command_umount, capture_output=True, shell=True) 
 
    return True

И, наконец, блок функции с подключением к почтовому серверу и отправкой сообщения:

Скрытый текст
def send_email(email_to: str, subject: str, message_text: str, attachment: bool): 
    """ 
    Отправляет письмо на почту 
 
    :email_to: Адрес почты пользователя/группы рассылки 
    :subject: Тема письма 
    :message_text: Готовое сообщение TXT 
    """ 
 
 
    message = MIMEMultipart("alternative") 
    message["Subject"] = subject 
    message["From"] = DEFINE_CRED_EMAIL["user"] 
    message["To"] = email_to 
 
    part1 = MIMEText(message_text, "plain") 
    message.attach(part1) 
 
    if attachment == True: 
        with open(DEFINE_LOG_FILE_176, "rb") as file: 
            part = MIMEBase('application', 'octet-stream') 
            part.set_payload(file.read()) 
            encoders.encode_base64(part) 
            part.add_header('Content-Disposition', f"attachment; filename=upgrade_to_176.log") 
        message.attach(part) 
 
    err_code = 0 
    try: 
 
        if DEFINE_CRED_EMAIL["smtp_ssl"].lower() == 'true' or DEFINE_CRED_EMAIL["smtp_ssl"].lower() == '1': 
            context = ssl.create_default_context() 
 
            with smtplib.SMTP_SSL(DEFINE_CRED_EMAIL["smtp_server"], 
                                  DEFINE_CRED_EMAIL["smtp_port"], 
                                  context=context) as server: 
 
                server.login(DEFINE_CRED_EMAIL["user"], DEFINE_CRED_EMAIL["password"]) 
                server.sendmail(DEFINE_CRED_EMAIL["user"], email_to, message.as_string()) 
                server.quit() 
                email_sended_print_msg = f"Email {subject} to {email_to} sended!" 
 
    except Exception as err: 
        print(f"Ошибка отправки оповещения на рассылку: {email_to}," 
              f"\nПодробности ошибки: {err}.") 

Перейдем к самой функции обновления ОС:

Скрытый текст
def upgrade_to_176(email_group_send: str, logger: logging): 
    """ 
    Обновление ОС до версии 1.7.6 
    :email_group_send: Группа рассылки для оповещения об статусе обнолвения АРМ 
    :logger: Объект класса для логирования операций 
    """ 
 
    command_update = "apt update -y" 
    command_upgrade = "astra-update -A -r -T" 
     
    if (get_fqdn() != "" and (get_os_version() != "" and get_os_version() != "1.7.6") and get_version_aldpro_client() != ""): 
        if validate_repos_and_backup(logger): 
            try: 
                with open("/etc/apt/sources.list", 'w') as file: 
                    file.write(DEFINE_REPO_ASTRA_BASE + "\n") 
                logger.info("Файл /etc/apt/sources.list - успешно изменен.") 
 
            except IOError as error: 
                text_email_false_edit_file = f"Добрый день! Произошла ошибка обновления ОС Astra Linux до версии 1.7.6," \ 
                                   f"\nАРМ: {get_fqdn()}," \ 
                                   f"\nАктуальная версия ОС: {get_os_version()}, " \ 
                                   f"\nАктуальная версия ядра: {get_kernel_version()}," \ 
                                   f"\nОшибка: {error}." 
                send_email(email_group_send, 
                           subject="Ошибка обновления АРМ", 
                           message_text=text_email_false_edit_file, 
                           attachment=True) 
                logger.error("Произошла ошибка открытия файла /etc/apt/sources.list") 
 
            try: 
                result_update = subprocess.run(command_update, capture_output=True, shell=True) 
                logger.info(":\n" + result_update.stdout.decode()) 
                result_upgrade = subprocess.run(command_upgrade, capture_output=True, shell=True) 
                logger.info(":\n" + result_upgrade.stdout.decode()) 
 
                if (result_update.returncode != 0 or result_upgrade.returncode != 0): 
                    result_add_ssh_user = subprocess.run(command_add_ssh_user, capture_output=True, shell=True) 
                    logger.info(":\n" + result_add_ssh_user.stdout.decode()) 
                    text_email_false = f"Добрый день! Произошла ошибка обновления ОС Astra Linux до версии 1.7.6," \ 
                                       f"\nАРМ: {get_fqdn()}," \ 
                                       f"\nАктуальная версия ОС: {get_os_version()} ," \ 
                                       f"\nАктуальная ядра ОС: {get_kernel_version()} ," \ 
                                       f"\nПодробнее об ошибке можно узнать в лог файле: {DEFINE_LOG_FILE_176}." 
                    send_email(email_group_send, 
                               subject="Ошибка обновления АРМ", 
                               message_text=text_email_false, 
                               attachment=True) 
                else: 
                    text_email_true = f"Добрый день! " \ 
                                      f"\nАРМ: {get_fqdn()}, c ОС Astra Linux успешно обновлен." \ 
                                      f"\nАктуальная версия ОС: {get_os_version()}," \ 
                                      f"\nАктуальная ядра ОС: {get_kernel_version()} ," \ 
                                      f"\nПодробнее об процессе обновления можно узнать в лог файле: {DEFINE_LOG_FILE_176}" 
                    send_email(email_group_send, 
                               subject="Успешное обновление АРМ", 
                               message_text=text_email_true, 
                               attachment=True) 
                    postinstall(logger) 
 
            except subprocess.CalledProcessError as eror_process: 
                text_email = f"Добрый день! Произошла ошибка обновления ОС Astra Linux 1.7.6, " \ 
                             f"\nАРМ: {get_fqdn()}." \ 
                             f"\nАктуальная версия ОС: {get_os_version()}," \ 
                             f"\nАктуальная ядра ОС: {get_kernel_version()} ," \ 
                             f"\nОшибка вызвана: {eror_process}." 
                send_email(email_group_send, 
                           subject="Ошибка обновления АРМ", 
                           message_text=text_email, 
                           attachment=True) 
 
        elif get_version_aldpro_client() == "": 
            logger.info("ALD Pro версия клиента не соответствует обновлению ОС до версии 1.7.6") 
        else: 
            text_email = f"Добрый день! Произошла ошибка обновления ОС Astra Linux 1.7.6, " \ 
                         f"\nАРМ: {get_fqdn()}." \ 
                         f"\nАктуальная версия ОС: {get_os_version()}," \ 
                         f"\nАктуальная ядра ОС: {get_kernel_version()} ," \ 
                         f"\nОшибка связана с валидацией репозиториев и созданием бэкапа." 
            send_email(email_group_send, 
                       subject="Ошибка обновления АРМ", 
                       message_text=text_email, 
                       attachment=True) 
    else: 
        logger.info("ОС уже обновлена до версии 1.7.6 | Агент не соответствует версии 2.4.0 | Не выполнен бэкап и валидация репозиториев") 

Для обновлений ОС есть рекомендации, какие репозитории необходимо указывать до момента обновления. В нашем случае при переходе с 1.7.5 на 1.7.6 необходим либо репозиторий «base», либо «main и update». Но помимо этих репозиториев есть еще и расширенный (extended), в котором иногда находятся пакеты с нужным программным обеспечением. В следующем блоке рассмотрим подключение этого репозитория: 

def postinstall(logger: logging): 
    """ 
    Добавляет расширенный репозиторий в ОС. 
    :logger: Объект класса для логирования операций 
    """ 
    command_update = "apt update -y" 
    try: 
        with open("/etc/apt/sources.list", 'w') as file: 
            file.write(DEFINE_REPO_ASTRA_BASE + "\n" + DEFINE_REPO_ASTRA_EXT) 
        logger.info("Файл /etc/apt/sources.list - успешно изменен, добавлен расширенный репозиторий.") 
 
    except IOError as error: 
        logger.error("Произошла ошибка открытия файла /etc/apt/sources.list - не добавлен расширенный репозиторий") 
    result_update = subprocess.run(command_update, capture_output=True, shell=True) 
    logger.info(":\n" + result_update.stdout.decode())

Ну и в заключение функция main:

if __name__ == '__main__': 
 
    logger = logging.getLogger() 
    logging.basicConfig(filename=DEFINE_LOG_FILE_176, level=logging.DEBUG,  format=f'%(asctime)s --- %(levelname)s --- %(message)s') 
 
    upgrade_to_176("upgrade@k2.tech", logger) 

Планы развития ALD Pro на 2025 год и перспективы

«Группа Астра» активно развивает ALD Pro. Согласно опубликованной дорожной карте (декабрь 2024), на 2025 год и далее запланированы важные улучшения: 

Улучшение управления и интерфейса: 

  • Новая консоль управления: появится динамическая форма для редактирования расширенных атрибутов объектов (пользователей, компьютеров, групп), которая существенно упростит работу администраторов по управлению сторонним программным обеспечением, которое при интеграции со службой каталога создает собственные атрибуты. 

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

  • Графическая утилита для ввода в домен: загрузка утилиты стала доступна из личного кабинета уже в рамках 2.5, а в версии 2.6 она должна будет войти уже в состав коробки и устанавливаться автоматически вместе с клиентской частью. 

  • Резервное копирование и восстановление: реализация встроенных функций бэкапа и восстановления (план на Q2-Q4 2025). 

Расширение экосистемы и мультивендорность: 

Поддержка ОС: клиентская часть для альтернативных вендоров Альт и РедОС в формате MVP доступна уже сейчас, а со второго квартала будет поставляться уже в коробке. Сейчас идет активная работа по проверке и адаптации параметров групповых политик, чтобы их можно было использовать в том числе и для управления этими системами. 

Безопасность: 

  • Интеграция PKI Proxy: расширение интеграции с внешними УЦ (CryptoPro, SafeTech). 

  • Усиление ГПО: доработка механизмов с учетом делегирования прав, внедрение LMI-фильтров (аналог WMI-фильтров в AD) для гибкой настройки условий применения политик. 

  • Защита контроллеров: планируется решение задачи по шифрованию системного диска контроллеров домена для возможности их размещения в физически незащищенных локациях без риска компрометации всего домена (план на 2027). 

  • Шифрование диска (аналог BitLocker): хранение ключей в TPM и LDAP (план на 2027). 

Оптимизация и новые возможности: 

  • Оптимизация репликации: для поддержки каталогов с десятками миллионов записей. 

  • Улучшение файловой службы: использование AutoFS для динамического монтирования общих папок (альтернатива DFS Namespaces). 

  • Репликация репозиториев: замена rsync на apt-mirror для создания зеркал внешних Debian-репозиториев и управляемой топологии репликации. 

Вместо заключения

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

Мы продолжим следить за развитием ALD Pro, внедрять и делиться своими наработками. Если у вас есть вопросы по внедрению, миграции или кастомизации продукта или собственный опыт работы с ним — расскажите в комментариях! 

Комментарии (4)


  1. RumataEstora
    14.05.2025 08:52

    Несколько замечаний

    1.

    #Получение короткого имени hostname
    hostnamectl set-hostname DC_NAME.DOMAIN_NAME

    В первом листинге: коммент не соответствует действию

    2.

    $IPADDR $SERVER_NAME.DOMAIN_NAME $PC_NAME

    Во втором листинге используется необъявленная переменная PC_NAME

    3.

    ALDPRO_CHECK=`dpkg -l | grep aldpro-client`
    if [[ -z $ALDPRO_CHECK ]];

    это можно упростить до

    if ! dpkg -l aldpro-client >/dev/null 2>&1
    

    4.

    -d "{
    "data": {

    Чтобы не мучиться с экранированием кавычек в кавычках, лучше вызватьcat в субшелле, например:

    -d "$(
      cat - <<-DATA
      {
        "data": {
        ...
    DATA
    )"

    5.

    обновление ОС - первый скрипт

    #Обновление ОС до актуальной версии
    apt update -y
    apt install astra-update -y && sudo astra-update -A -r -T

    обновление ОС - второй скрипт

    #Установка пакетов ALD Pro
    apt update -y
    DEBIAN_FRONTEND=noninteractive apt-get install -q -y aldpro-client

    Получается на второй машине почему-то игнорируется обновление системы. И вообще в целом, обновление репозиториев выстроено нелогично. Лучше прописать все источники в sources.list и//или sources.list.d/, а потом выполнить обновления репо, системы и установку нужных пакетов.

    Дальше уже читал по диагонали.


    1. UsovA Автор
      14.05.2025 08:52

      Большое спасибо за ваш комментарий!
      1) Исправил - согласен с вами!
      2) Исправил - согласен с вами!
      3) Эта конструкция возможно будет удобнее, протестирую.
      4) Эта конструкция возможно будет удобнее, протестирую.
      5) Не согласен - по требованиям вендора необходимо использовать последовательность и конфигурационные файлы согласно скриптам.


      1. RumataEstora
        14.05.2025 08:52

        По п5. Все верно. Редактируете /etc/apt/sources.list и /etc/apt/sources.list.d/* - пишете туда все, что вам требуется, а потом apt update ; apt dist-upgrade -y ; apt install все что надо для счастья . Или вендор рекомендует другой путь?


        1. AnatolijL
          14.05.2025 08:52

          В большинстве случаев dist-upgrade справится с задачей, но лучше все-таки использовать штатную утилиту astra-update, как указано в статье, т.к. она реализует предусмотренную разработчиками ОС стратегию обновления пакетов и может отключать/включать собственные функции безопасности, что может быть необходимо для корректного обновления системы.