Привет, Хабр! На связи Александр Усов, системный инженер в K2Tex. В своей предыдущей статье я уже делал подробный обзор фич ALD Pro и их особенностей, с которыми регулярно сталкиваюсь. Сегодня хочу поделиться тем, чему мы учим администраторов заказчиков: как реально эксплуатировать эту систему, а не просто развернуть и оставить на холостом ходу. Разберу, как устроены «расширенные атрибуты» и почему следует избегать одинаковых названий отделов в оргструктуре, какую функциональность ALD Pro унаследовал от FreeIPA, а в чем превзошел, и каким образом эффективнее организовать журналирование событий.

Вспоминаю себя стажером, который едва слышал о Linux-системах. Что именно слышал? Что существует Red Hat Linux, у которой черная консоль и ничего больше, веб-сервер или реверс-прокси nginx/apache2, кеширующий DNS-сервер BIND (да и тот с костылями).

Более глубокое погружение в эту область началось со сдачи сертификации Red Hat и изучения ОС CentOS. И это был классный опыт обучения от зарубежного вендора! Во-первых, подход «минимум теории и максимум практики» (оговорюсь сразу, что в самих материалах теории было более чем достаточно). Курс строился не на базовом изучении Linux, а на его конфликтах внутри, когда одна и та же сущность внутри ОС может быть использована по-разному. Во-вторых, запомнился процесс сертификации: это был не просто тест или разговор с преподавателем, а две ВМ и ряд заданий, которые необходимо было выполнить внутри них. Оценивали строго: например, если не выполнил сброс пароля администратора, то не сможешь дальше запускать команды с правами суперпользователя через sudo. 

Когда вендор ушел с российского рынка, достаточно быстро появилась достойная замена: «Группа Астра» выпустила бесплатный курс, посвященный двум большим темам – работе ОС и службе каталогов ALD Pro. Я сам регулярно обращаюсь к нему и рекомендую администраторам заказчиков. Очень достойный курс по Linux.  Отдельно бы выделил, что вендор не просто рассказывает и учит работать с его продуктами, а проводит сравнение с работой аналогичных механизмов в Microsoft. Курс доступен по ссылке:  https://www.aldpro.ru/professional/. Всем советую!

Когда я начал осваивать ALD Pro, меня вдохновила сама идея этого продукта и амбициозные планы разработчиков сделать полноценный аналог MS AD и Windows Server на Linux. По мере более глубокого погружения я открывал новые особенности и сегодня поделюсь с вами некоторыми из них.

Расширенные атрибуты

В MS AD есть расширенные атрибуты (Extension Attributes 1-15), которые предназначены для хранения пользовательских данных, не входящих в стандартную схему. Эти атрибуты используются для дополнительной информации о пользователях и часто применяются при интеграции с внешними бизнес-системами.

В ALD Pro тоже предусмотрена возможность создания дополнительных атрибутов (в интерфейсе ALD Pro они находятся на вкладке «Дополнительные сведения») для объектов типа УЗ пользователей, но со значительными отличиями от MS AD.

Давайте посмотрим, как это выглядит под капотом решения:

Для создания дополнительного атрибута пользователя в главном окне ALD Pro необходимо перейти на вкладку «Управление доменом» -> «Пользователи и группы» -> «Атрибуты пользователей» -> «+ Новый атрибут»:

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

Для заполнения данного атрибута в карточке пользователя необходимо перейти на вкладку «Дополнительные сведения»:

Заполнение дополнительных атрибутов пользователя
Заполнение дополнительных атрибутов пользователя

Теперь рассмотрим их отображение в редакторе LDAP. Все УЗ пользователей находятся в контейнере «cn=users,cn=accounts,<суффикс домена>»:

Контейнер для хранения объектов типа «пользователь»
Контейнер для хранения объектов типа «пользователь»

Выберем УЗ, в которой заполнены дополнительные сведения:

Полное значение:

user-custom-attr: {"uuid": "259293e7-bddb-46e1-9647-24a245819f9f", "description": "Drivers License", "oid": "1.3.6.1.4.1.1466.115.121.1.15", "type": "\u0421\u0442\u0440\u043e\u043a\u0430", "value": "321321312"}

user-custom-attr: {"uuid": "06c83758-5eab-4284-b801-117262d36eba", "description": "Employees office", "oid": "1.3.6.1.4.1.1466.115.121.1.15", "type": "\u0421\u0442\u0440\u043e\u043a\u0430", "value": "Moscow, Pushkina st, d.11"}

Как видно выше, атрибут user-custom-attr допускает множество значений: объект JSON содержит значение дополнительного атрибута пользователя вместе с дополнительной метаинформацией, поэтому в случае интеграции потребуется выполнять парсинг и на стороне бизнес-систем. Конечно, можно добавить в схему LDAP-каталога свои атрибуты, чтобы работать с ними из скриптов как с обычными, но в веб-интерфейсе они будут недоступны. У нас даже был написан скрипт, с помощью которого можно записывать значения обычных LDAP-атрибутов, синхронизируемых из MS AD, в дополнительные атрибуты ALD Pro. Забирайте и не благодарите.

custom_sss.py – сортировка ответов LDAP (дополнительный скрипт):

from ldap.controls import RequestControl
from ldap.controls.sss import SSSRequestControl

class SSSRequestControl(SSSRequestControl):
    """Order result server side

    >>> s = SSSRequestControl(ordering_rules=['-cn'])
    """

    controlType = "1.2.840.113556.1.4.473"

    def __init__(
        self,
        criticality=False,
        ordering_rules=None,
    ):
        RequestControl.__init__(self, self.controlType, criticality)
        self.ordering_rules = ordering_rules
        if isinstance(ordering_rules, str):
            ordering_rules = [ordering_rules]
        for rule in ordering_rules:
            rule = rule.split(":")
            assert len(rule) < 3, "syntax for ordering rule: [-]<attribute-type>[:ordering-rule]"

custom-attr-sync.py – скрипт синхронизации атрибутов:

Скрытый текст
################################################################################################################
# Скрипт позволяет записывать значения LDAP-атрибутов в пользовательские атрибуты user-custom-attr

# Пример вызова скрипта:
# sudo python3 custom-attr-sync.py
################################################################################################################

# Параметры для синхронизации атрибутов
sync_attrs = [
    {
        # Название атрибута, значение которого является источником
        'source_attribute_name': 'gecos',
        # Значение атрибута user-custom-attr, вместо шаблона #SOURCE_ATTRIBUTE_VLAUE# будет подставлено значение атрибута source_attribute_name
        # Вы можете добавить пользовательский атрибут к учетной записи пользователя и скопировать пример готового шаблона из LDAP
        'user_custom_attr': '{"uuid": "3affc7f6-9148-4b91-b795-30322ad8fa76", "description": "gecos", "oid": "1.3.6.1.4.1.1466.115.121.1.15", "type": "\u0421\u0442\u0440\u043e\u043a\u0430", "value": "#SOURCE_ATTRIBUTE_VLAUE#"}'
    }
]

# Файл, в котором будет находиться параметр next_entry_usn с значением entryUsn, с которого нужно продолжить синхронизацию
sync_cache_file = '/tmp/custom-attr-sync-cache'

# Количество записей, которое должно обрабатываться за один вызов скрипта
batch_size = 1000

################################################################################################################

from configparser import ConfigParser
from ipaplatform.paths import paths

import ldap
from custom_sss import SSSRequestControl
from ldap.controls.pagedresults import SimplePagedResultsControl

import json

# Считываем настройки IPA из конфигурационного файла

# IPA_DEFAULT_CONF = /etc/ipa/default.conf
conf_file = paths.IPA_DEFAULT_CONF
conf_parser = ConfigParser(interpolation=None)
conf_parser.read(conf_file)

# ldap_uri = ldapi://%2Frun%2Fslapd-ALD-COMPANY-LAN.socket
ldap_uri = conf_parser.get("global", "ldap_uri", fallback=None)

print(f'Подключение будет выполняться по Unix-сокету {ldap_uri}')

# basedn = dc=ald,dc=company,dc=lan
basedn = conf_parser.get("global", "basedn", fallback=None)
search_dn = f"cn=users,cn=accounts,{basedn}"

print(f'Поиск пользователей будет осуществляться в записи {search_dn}')

# Выполняем подключение к серверу
try:
    conn = ldap.initialize(ldap_uri)
    conn.sasl_non_interactive_bind_s('EXTERNAL')
    print('Подключение к серверу выполнено успешно')
except Exception as e:
    print('Ошибка подключения к серверу')
    exit()

search_scope = ldap.SCOPE_ONELEVEL

# Считываем значение next_entry_usn, с которого нужно продолжить
sync_cache_parser = ConfigParser(interpolation=None)
sync_cache_parser.read(sync_cache_file)
try:
    next_entry_usn = sync_cache_parser.getint('DEFAULT', 'next_entry_usn', fallback=0)
except ValueError as e:
    next_entry_usn = 0

print(f'Поиск изменений будет выполнен, начиная с entryUsn={next_entry_usn}')
print(f'Количество записей в одном пакете {batch_size}')

search_filter = f"(entryUsn>={next_entry_usn})"

search_attrs = ['entryUsn','user-custom-attr']

# Добавляем атрибуты из параметров трансформации
for sync_attr in sync_attrs:
    search_attrs.append(sync_attr['source_attribute_name'])

sp_ctrl = SimplePagedResultsControl(True, size=batch_size, cookie="")

sss_ctrl = SSSRequestControl(
        True,
        ordering_rules=[f"entryUsn"],
    )

# Извлекаем данные из каталога

try:
    msgid = conn.search_ext(
            search_dn,
            search_scope,
            search_filter,
            attrlist=search_attrs,
            serverctrls=[sp_ctrl, sss_ctrl],
        )
    result = conn.result3(msgid)
    entries = result[1]
except Exception as e:
    print('Ошибка извлечения данных')
    exit()

if len(entries)!=0:
    print(f'Записей, требующих проверки {len(entries)}')
else:
    print('Измененных записей не найдено')
    exit()

# Формируем список, включающий uid тех пользовательских атрибутов, которые должны синхронизироваться автоматически
sync_attr_uids = []
for sync_attr in sync_attrs:
    sync_attr_uids.append(json.loads(sync_attr['user_custom_attr'])['uuid'])

# Выполняем обработку записей

for entry in entries:
    entry_dn = entry[0]
    entry_attrs = entry[1]
    next_entry_usn = 1 + int(entry_attrs['entryUsn'][0])

    new_user_custom_attr_values = []

    user_custom_attr_values = entry_attrs.get('user-custom-attr', [])
    if user_custom_attr_values is None:
        user_custom_attr_values = []

    # Добавляем существующие пользовательские атрибуты, которые не затронуты синхронизацией
    for user_custom_attr in user_custom_attr_values:
        user_custom_attr_str = user_custom_attr.decode()
        user_custom_attr_json = json.loads(user_custom_attr_str)
        uuid = user_custom_attr_json['uuid']
        if uuid not in sync_attr_uids:
            new_user_custom_attr_values.append(user_custom_attr)

    # Добавляем пользовательские атрибуты по шаблону
    for sync_attr in sync_attrs:
        if sync_attr['source_attribute_name'] in entry_attrs.keys():
            source_attribute_pattern = sync_attr['user_custom_attr']
            # Склеиваем список из байтовых массивов в одну строку через запятую
            source_attribute_value =  b', '.join(entry_attrs[sync_attr['source_attribute_name']]).decode()
            source_attribute = source_attribute_pattern.replace("#SOURCE_ATTRIBUTE_VLAUE#", source_attribute_value).encode("unicode-escape")
            new_user_custom_attr_values.append(source_attribute)

    # Определяем, нужно ли обновить атрибут в учетной записи
    custom_attrs_changed = False
    if len(user_custom_attr_values) != len(new_user_custom_attr_values):
        custom_attrs_changed = True

    for user_custom_attr in user_custom_attr_values:
        if user_custom_attr not in new_user_custom_attr_values:
            custom_attrs_changed = True

    # Обновляем запись при необходимости
    try:
        if custom_attrs_changed:
            mod_attrs = [(ldap.MOD_REPLACE, 'user-custom-attr', new_user_custom_attr_values)]
            conn.modify_s(entry_dn, mod_attrs)
            print(f'{entry_dn} - запись обработана')
        else:
            print(f'{entry_dn} - запись не требует обновления')
    except ldap.LDAPError as e:
        print(f'Ошибка обновления атрибутов для записи {entry_dn}')

# Записываем значение next_entry_usn, с которого нужно будет начать в следующий раз
sync_cache_parser.set('DEFAULT', 'next_entry_usn', str(next_entry_usn))
with open(sync_cache_file, 'w') as fp:
    sync_cache_parser.write(fp)

Необходимо поместить два скрипта в одну директорию на контроллере домена и запустить командой:

sudo python3 custom-attr-sync.py

Будьте внимательны: если используете кириллицу в тексте или в названиях атрибутов, их значения будут кодироваться в base64, как например, в ключе «type».

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

Для перехода к веб-интерфейсу FreeIPA откройте страницу по URL «https://<FQDN_КД>/ipa/ui/»:

Для редактирования УЗ пользователя необходимо перейти на вкладку «Идентификация» -> «Пользователи» -> «Имя пользователя»:

В представленном выше интерфейсе отображается чуть больше атрибутов УЗ пользователя, которые могут быть полезны для ваших задач. Но ещё больше атрибутов вы сможете найти в схеме LDAP-каталога, так как FreeIPA наследует множество классов от предшествующих систем. В 2026 году вендор запланировал добавить расширенный редактор атрибутов в ALD Pro. Через него можно будет изменять любые LDAP-атрибуты пользователей, компьютеров и других типов объектов, которые есть в службе каталогов.

Служба каталогов FreeIPA отличается от MS AD отсутствием оргструктуры – все  политики назначаются на группы пользователей и компьютеров. Продукт ALD Pro расширяет возможности FreeIPA, добавляя организационные подразделения (OU), на которые можно назначать объекты групповых политик. 

При настройке оргструктуры в ALD Pro следует учитывать несколько важных моментов. Например, при назначении OU на объект вам доступен инструмент поиска, но все найденные подразделения будут отображаться плоским списком, что усложняет работу при наличии одноименных подразделений в нескольких офисах. Если вы не хотите оказаться в ситуации, представленной на скрине ниже, старайтесь использовать для ваших подразделений уникальные наименования:

На скриншоте видим два подразделения с названием «Департамент маркетинга». В интерфейсе управления подразделениями все отображается корректно. Проблема проявится при переходе в оснастку управления группами и попытке назначить новой группе подразделение:

Можно выбирать каждое из них и смотреть, что будет указано в следующем поле «Расположение»:

По нему можно определить расположение в LDAP для данного подразделения. А что делать, если у вас множество подразделений с одинаковыми названиями? Перебирать каждое крайне неудобно, поэтому лучше на старте именовать их правильно. В карточке  «Пользователь» разработчики данную проблему уже решили, предусмотрев  удобный интерфейс выбора подразделений:

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

Приступим к выбору алфавита. Как вы видели ранее, при использовании кириллицы LDAP-сервер будет отдавать значения в кодировке base64. 

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

Предварительно создадим подразделение. Для этого в веб-интерфейсе администрирования проходим путь: «Пользователи и компьютеры» -> «Организационная структура» -> «Название домена» -> «+ Подразделение»:

Теперь посмотрим его отображение в LDAP – все подразделения хранятся в контейнере «cn=orgunits,cn=accounts, dc=<домен>»:

Выполним команду:

ldapsearch -x -b "cn=orgunits,cn=accounts,dc=aldpro,dc=izpo,dc=me" -H ldap://127.0.0.1 -W -D "uid=admin,cn=users,cn=accounts,dc=aldpro,dc=izpo,dc=me" "ou=*"

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

Попробуем с помощью REST-API получить данные о подразделении:

#!/bin/bash

curl -k -X 'POST' \
    'https://dc1.aldpro.izpo.me/ad/api/ds/login' \
    -H 'accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
    "data": {
        "login": "admin",
        "password": "**********"
    }
}' -c curl.cookie


curl -k -X 'GET' \
 'https://dc1.aldpro.izpo.me/ad/api/ds/organizational-units' \
 -H 'accept: application/json' \
 -H 'Content-Type: application/json' \
 -b curl.cookie

Строки, содержащие символы кириллицы, отдаются корректно в кодировке UTF-8. А если сделать «POST-запрос» для создания нового подразделения?

Попробуем с помощью REST-API из скрипта:

#!/bin/bash

curl -k -X 'POST' \
    'https://dc1.aldpro.izpo.me/ad/api/ds/login' \
    -H 'accept: application/json' \
    -H 'Content-Type: application/json' \
    -d '{
    "data": {
        "login": "admin",
        "password": "*********"
    }
}' -c curl.cookie


curl -k -X 'POST' \
 'https://dc1.aldpro.izpo.me/ad/api/ds/organizational-units' \
 -H 'accept: application/json' \
 -H 'Content-Type: application/json' \
 -d '{
        "data": {
        "organizationunit_display_name": "Новое подразделение 2",
        "organizationunit_parent_dn": "ou=Новое подразделение,ou=aldpro.izpo.me,cn=orgunits,cn=accounts,dc=aldpro,dc=izpo,dc=me",
        "organizationunit_description": "Описание подразделения",
        "organizationunit_manager_dn": "uid=admin,cn=users,cn=accounts,dc=aldpro,dc=izpo,dc=me"
    }
}' -b curl.cookie

Видим, что и в POST-запросах через REST-API кириллица обрабатывается и отображается корректно.

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

  1. при обращении по LDAP значение атрибута, отвечающего за название (ou), хранится и отображается в формате base64;

  2. при обращении с помощью REST-API  используется привычная кодировка UTF-8.

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

Полезные функции ALD Pro, унаследованные от FreeIPA

ALD Pro – это отдельное отечественное решение, в основу которого лег проект с открытым исходным кодом – служба каталогов FreeIPA.

Во FreeIPA есть множество интересных фишек, которые помогают администратором управлять инфраструктурой Linux (не только в экосистеме «Группы Астра»), которыми я делюсь с нашими заказчиками.

Политики FreeIPA

Наличие групповых политик для управления окружением пользователей и компьютеров – одно из отличий ALD Pro (за что спасибо вендору и его огромному вкладу в развитие решения) от FreeIPA:

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

Однако ALD Pro унаследовал от FreeIPA три типа крайне полезных и эффективных политик безопасности:

  • HBAC (Host Based Access Control) – управление гранулярным доступом к сервисам на доменном компьютере; 

  • SUDO – управление повышением привилегий пользователей на доменных компьютерах;

  • Password Policy – политики паролей пользователей.

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

Начну с первого типа политик – HBAC. Они имеют довольно широкую функциональность гранулярности управления доступом в ОС для определенных пользователей или групп. 

Администратор может не только ограничивать или предоставлять возможность конкретным пользователям входить на определённый АРМ, но и делать это гранулярно на уровне конкретных сервисов в ОС. Например, пользователю АУсов можно разрешить вход на АРМ Client01 только в графическую оболочку, а SSH запретить, а для PC2 – наоборот. Тем самым в инфраструктуре появляется гибкая возможность управлять доступом пользователей к отдельным АРМ или серверам.

В случае второго типа политик – SUDO, можно гранулярно разрешать выполнение команд через утилиту sudo с правами root на конкретных АРМ. Представим, что вы администратор, который обслуживает большое количество серверов и АРМ. Чтобы не править на каждом сервере файл /etc/sudoers, можно предоставить определенные привилегии из интерфейса ALD Pro. Самый распространённый кейс: разрешить Администраторам ИБ запускать только определенные команды – например, «sudo cat /var/log/auth.log». 

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

/usr/bin/cat /var/log/auth.log*

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

sudo /usr/bin/cat /var/log/auth.log /etc/shadow | tail -n 5

В данном случае лучше использовать ACL и предоставить права на папку «/var/log» для определённой группы/пользователя с помощью кастомной групповой политики.

SSH-атрибут

Я не знаю администраторов, которые не используют SSH для администрирования серверов Linux. С помощью этого прекрасного протокола можно не только безопасно подключаться к консоли сервера и работать с командной оболочкой, но и отправлять файлы (SCP), пробрасывать туннели.

Хорошая практика – уходить от использования паролей и переходить на SSH-ключи. Во FreeIPA у пользователя есть специальный атрибут «ipaSshPubKey»:


 

Данный атрибут хранится как бинарный, содержащий открытый SSH-ключ. Это позволяет обеспечить гибкость и поддержку различных типов SSH-ключей, таких как RSA, ECDSA или ED25519.

Такой подход к хранению ключей позволяет серверам с помощью клиента SSSD взаимодействовать с ALD Pro/FreeIPA через LDAP, чтобы легко получать открытые ключи для проверки подлинности пользователя при подключении по SSH на сервер. 

  • На всех хостах в домене ALD Pro по умолчанию настроена аутентификации по SSH-ключам:

#nano /etc/ssh/sshd_config

PubkeyAuthentication yes
ProxyCommand /usr/bin/sss_ssh_knownhostsproxy -p %p %h
GlobalKnownHostsFile /var/lib/sss/pubconf/known_hosts

#systemctl restart ssh
  • При этом в УЗ пользователя в ALD Pro («Пользователи и компьютеры» -> «Пользователи» -> «Имя пользователя») в атрибут «Ключ в формате SSH» нужно добавить открытую часть ключа пользователя:

  • На доменных компьютерах появится возможность аутентификации по SSH-ключу, если это не противоречит политикам HBAC: 

Таким образом, ключи не потребуется хранить в файлах на всех серверах и рабочих станциях, достаточно будет загрузить публичную часть ключа в атрибут УЗ пользователя в ALD Pro. Теперь представьте, насколько упрощается процесс обновления и выпуска новых ключей. Но что еще круче – в домене ALD Pro у всех хостов уже есть собственные SSH-ключи, и их открытая часть загружена в каталог — это позволяет подключаться к любому компьютеру и серверу без проверки фингерпринта.

Хранение ключей в каталоге, возможность использования политик HBAC и SUDO позволяет существенно повысить безопасность администрирования Astra Linux и других операционных систем в домене ALD Pro на базе FreeIPA.

Двухфакторная аутентификация (2FA)

Еще интересная фишка FreeIPA – это управление двухфакторной аутентификацией под доменной УЗ с помощью OTP. В крайней версии ALD Pro эту функцию из портала управления еще не получится настроить, потребуется обращаться к командной строке или веб-интерфейсу FreeIPA, но официальная поддержка OTP уже заявлена и по планам вендора ожидается в Q3-Q4 2026 года.

А что если OTP будет недостаточно? На проекте для одного из заказчиков я столкнулся с требованием использовать второй фактор для административных учетных записей в службе каталогов. Пришлось рассматривать следующие варианты:

  1. Reverse Proxy;

  2. SSO-провайдер.

В первом случае устанавливается отдельный сервер, который выступает в роли балансировщика с ограничением на доступ к URI, относящимся к администрированию ALD Pro, только из конкретных подсетей. То есть администратор не только должен знать логин и пароль, но и находиться в определенной подсети или иметь разрешенный IP-адрес (строго говоря, это не совсем 2FA). 

Этот подход имеет как свои плюсы:

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

  • простота реализации;

  • возможность авторизоваться в портале пользователя для недоверенных сетей (например, для смены пароля).

Так и минусы:

  • неудобно администрировать (нужно вносить конфигурацию вручную) и перезапускать веб-сервис на сервере Reverse Proxy после каждого изменения конфигурации веб-сервис на сервере Reverse Proxy;

  • новые версии продукта придется тестировать и поддерживать самостоятельно. Так как фильтрация настраивается на определенные URI, число которых может быть расширено;

  • необходимость ограничивать доступы напрямую к КД от АРМ по HTTPS на сетевом оборудовании, чтобы они ходили только через Reverse Proxy;

  • отсутствует второй фактор как таковой (это скорее костыльный путь).

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

Скрытый текст
# /etc/nginx/nginx.conf

user  www-data;
worker_processes auto;
pid /run/nginx.pid;

events {
    worker_connections 1024;
}

http {
    include       /etc/nginx/mime.types;
    default_type  application/octet-stream;
    sendfile      on;

    # Кеширование map для производительности
    map_hash_bucket_size 128;
    map_hash_max_size 2048;

    # --- доверенные / недоверенные сети ---
    geo $access_level {
        default         deny;

        # доверенные
        172.25.50.0/24  allow;
        172.25.50.82    allow;
        127.0.0.1       allow;
        ::1             allow;

        # недоверенные
        10.22.11.0/24   deny;
        100.64.8.6      deny;

        # исключения в подсети
        10.22.11.53     allow;
    }

    # --- map: переводим в HTTP-код ---
    map $access_level $deny_code {
        deny   403;
        allow  0;
    }

    # --- upstream: FreeIPA ---
    upstream freeipa_backend {
        # ip_hash; # лучше убрать, т.к. несовместим с keepalive
        server dc1.aldpro.izpo.me:443;
        server dc2.aldpro.izpo.me:443;
        keepalive 16;
    }

    # --- основной сервер ---
    server {
        listen 443 ssl;
		http2 on;
        server_name proxy01.aldpro.izpo.me;

        ssl_certificate     /etc/nginx/ssl/mail01.aldpro.izpo.me.crt;
        ssl_certificate_key /etc/nginx/ssl/mail01.aldpro.izpo.me.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384';
        ssl_prefer_server_ciphers on;

        # SSL оптимизация
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;
        ssl_session_tickets off;

        add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
        add_header X-Frame-Options "SAMEORIGIN" always;
        add_header X-Content-Type-Options "nosniff" always;
        add_header Referrer-Policy "strict-origin-when-cross-origin" always;
        server_tokens off;

        # --- общие proxy-настройки ---
        proxy_http_version 1.1;
        proxy_set_header Host                 $host;
        proxy_set_header X-Real-IP            $remote_addr;
        proxy_set_header X-Forwarded-For      $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto    $scheme;
        proxy_set_header X-Forwarded-Host     $host;
        proxy_set_header Connection           "";
        proxy_intercept_errors                on;

        proxy_ssl_server_name on;
        proxy_ssl_name dc1.aldpro.izpo.me;
        proxy_ssl_verify off;

        proxy_read_timeout  120s;
        proxy_send_timeout  120s;
        proxy_buffering on;

        proxy_redirect      https://dc1.aldpro.izpo.me/ https://$host/;
        proxy_redirect      https://dc2.aldpro.izpo.me/ https://$host/;

        # --- нормализация URL ---
        location = / {
            return 302 https://$host/ad/ui/;
        }

        location = /ad/ui {
            return 301 https://$host/ad/ui/;
        }

        # 1. Статические файлы
        location ~ ^/ad/ui/.*\.(js|css|png|jpg|jpeg|svg|woff2|ico|ttf)$ {
            proxy_pass https://freeipa_backend;
            expires 1y;
            add_header Cache-Control "public, immutable";
        }

        # 2. WebSocket
        location ~ ^/ad/services/ws/ {
            proxy_pass https://freeipa_backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
        }

        # 3. UI manifests
        location = /ad/api/core/ui-manifests/ {
            if ($deny_code) {
                add_header Content-Type application/json;
                add_header Cache-Control "no-store, no-cache, must-revalidate";
                return 200 '{"data":[{"id":"profile","moduleName":"ProfileFrontend","entrypoint":"profile-frontend.js","menuItem":{"path":"/profile","label":"Личный кабинет","description":"Управление персональными настройками","icon":"profile","options":[{"label":"Мой профиль","path":"/configuration"}]},"routerPath":"/profile","weight":0}],"total":1,"success":true}';
            }
            proxy_pass https://freeipa_backend;
        }

        # 4. Блокируемые API модули
        location ~ ^/ad/api/(domainmgmt|uc|gp|software|automation|services|monitoring|audit|syncer)/ {
            if ($deny_code) {
                add_header Content-Type application/json;
                return 403 '{"error": "Access denied", "code": 403, "message": "Module access restricted"}';
            }
            proxy_pass https://freeipa_backend;
        }

        # 5. Блокируемые DS API
        location ~ ^/ad/api/ds/ {
            if ($deny_code) {
                add_header Content-Type application/json;
                add_header Cache-Control "no-store, no-cache, must-revalidate";
                return 200 '{"result": {"data": [], "total_count": 0}, "success": true}';
            }
            proxy_pass https://freeipa_backend;
        }

        # 6. Блокируемые UC API
        location ~ ^/ad/api/uc/ {
            if ($deny_code) {
                add_header Content-Type application/json;
                add_header Cache-Control "no-store, no-cache, must-revalidate";
                return 200 '{"result": {"data": [], "total_count": 0}, "success": true, "enabled": false}';
            }
            proxy_pass https://freeipa_backend;
        }

        # 7. Блокируем UI модули
        location ~ ^/ad/ui/(domainmgmt|uc|gp|software|automation|services|monitoring|audit|syncer)/ {
            if ($deny_code) {
                add_header Content-Type application/json;
                return 403 '{"error": "Module access denied"}';
            }
            proxy_pass https://freeipa_backend;
        }

        # 8. UC роуты - редирект
        location ~ ^/ad/ui/uc/([^.?]+)$ {
            if ($deny_code) {
                return 302 https://$host/ad/ui/#/profile/;
            }
            proxy_pass https://freeipa_backend;
        }

        # 9. Всё остальное под /ad/
        location ^~ /ad/ {
            proxy_pass https://freeipa_backend;
        }

        # 10. Fallback
        location / {
            if ($deny_code) {
                return 302 https://$host/ad/ui/;
            }
            proxy_pass https://freeipa_backend;
        }
    }

    # --- редирект HTTP → HTTPS ---
    server {
        listen 80;
        server_name proxy01.aldpro.izpo.me;
        return 301 https://$host$request_uri;
    }
}

Во втором же случае перед КД ALD Pro ставится сервер SSO, который после выполнения двухфакторной аутентификации администратора перенаправляет его на административный интерфейс ALD Pro. В качестве SSO-провайдера используется Avanpost FAM с поддержкой функциональности для приложений, у которых не предусмотрена интеграция по SAML/OpenID – Reverse Proxy.

Архитектурно данный вариант похож на первый, но имеет свои преимущества:

  • поддержка полноценного второго фактора;

  • гибкое управление доступом (синхронизация с доменом и возможность разрешения/запрета для входа на портал ALD Pro);

Из минусов:

  • аналогично первому решению – необходимость ограничивать доступы напрямую к КД от АРМ;

  • отдельное платное решение (но с большим функционалом SSO-провайдера).

После рассмотрения данных вариантов «Группа Астра» реализовала в версии ALD Pro 3.1.0 поддержку 2FA с помощью OTP-токенов для аутентификации на портале (с управлением токенами через веб-интерфейс FreeIPA) и анонсировала полную поддержку с управлением 2FA через портал ALD Pro в 2026 году. Данная функциональность позволит настраивать второй фактор для УЗ пользователей, генерировать и управлять жизненным циклом токенов из веб-интерфейса ALD Pro.

Преимущества данного подхода:

  • встроенная функциональность (отсутствуют расходы на сторонние решения);

  • официальная поддержка на техническом уровне уже сейчас и возможность управлять токенами OTP через портал управления ALD Pro с 2026 года;

  • распространяется не только на вход в интерфейс администрирования, но и вход на доменный компьютер (включая протоколы SSH, RDP).

Среди явных недостатков могу отметить:

  • поддержка на техническом уровне только с версии 3.1.0 (не все успели еще обновиться);

  • админам, для которых установлено требование OTP, придется освоить «Kerberos armoring», иначе Kerberos-аутентификация по OTP не работает. 

В этом случае аутентификация выполняется в два шага: сначала нужно получить анонимный TGT-билет и сохранить его в файл:

kinit -n -c FILE:/tmp/$USER_armor.cache

Затем, используя этот кеш, выполнить аутентификацию:

kinit -T /tmp/$USER_armor.cache myadmin

Кстати, если для учетки администратора OTP не установлен, то защищать аутентификацию с помощью Kerberos Armoring нужно тем более. Берите на заметку!

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

FreeIPA поддерживает TOTP и HOTP. В случае TOTP механизм работает примерно следующим образом:

Сервер с клиентом договариваются о том, что в качестве второго фактора аутентификации будут использовать последние шесть цифр из числа, которое можно получить умножением количества минут, прошедших с начала эпохи Linux, на число Х (допустим, 15). На часах 8:16, 23 октября 2025 года, поэтому берём значение 29 493 600, умножаем его на 15 и получаем 442 404 000. Оставляем только последние шесть цифр и получаем OTP-код 404 000). Приведенные расчеты можно произвести без обращения к третьей стороне, для этого нужно знать только параметры генерации кода. Множитель Х и количество цифр в одноразовом пароле и есть параметры генерации OTP, которые можно передать по дополнительному каналу связи в виде строки или QR-кода. На самом деле механизм чуть сложнее, но такого понимания на первое время будет вполне достаточно.

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

1) в веб-интерфейсе FreeIPA перейти по пути «Аутентификация» -> «Токены ОТР» -> «Добавить»;

2) в открывшейся форме заполнить поля;

Скрытый текст

3) заполнить «Уникальный идентификатор» -> название, начало и окончание срока действия, выбрать необходимый алгоритм и количество цифр, и нажать кнопку «Добавить»;

4) настроить токен;

Скрытый текст

5) считать QR-код с любого клиента OTP (рекомендую Яндекс ID);

Скрытый текст

Затем в веб-интерфейсе FreeIPA перейти по пути: «Идентификация» -> «Пользователи» -> «Необходимый пользователь», в поле «Тип аутентификации» указать «Двухфакторная аутентификация (Пароль + OTP)» и нажать кнопку «Сохранить»:

Готово. Теперь можно входить в веб-интерфейс ALD Pro/FreeIPA, в операционную систему доменного компьютера и даже подключаться по нему по RDP с использованием второго фактора.

Отмечу, что данная функциональность будет востребована только для административных УЗ. При аутентификации обычных пользователей в сторонних приложениях не способных удерживать авторизованную сессию, могут возникать проблемы. Например, пользователь не сможет сохранить пароль в почтовом клиенте, так как OTP-код будет действителен только один раз, и ему придется вводить пароль каждый раз при отправке/получении почты.

Automember

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

Настройками правил автоучастия можно управлять через веб-интерфейс FreeIPA или с помощью консольной утилиты IPA. Механизм работы следующий: администраторы создают правила автоматического участия. Эти правила определяют условия, на основании которых пользователи или хосты будут автоматически добавляться в соответствующие группы. Условия основаны на значениях атрибутов LDAP:

В правиле есть как включающие, так и исключающие выражения, причем вторые имеют приоритет.

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

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

  • Вызов плагина automember происходит при создании или изменении учётной записи.

  • Плагин automember проверяет атрибуты учетной записи по правилам автоучастия и добавляет ее в те группы, по которым проверка будет пройдена успешно.

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

  • Включающие правила определяют, какие учетные записи должны быть автоматически добавлены в группу, а исключающие – определяют, какие учетные записи НЕ должны автоматически добавляться в группу. Это позволяет уточнить выборку, сформированную включающими правилами.

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

Логика удаления учетных записей из групп с автоучастием:

  • Учетная запись будет удалена из лишних групп, которым она больше не соответствует по правилам автоучастия, если УЗ запись останется участником хотя бы одной группы с автоучастием.

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

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

kinit <УЗ с административными правами>
ipa automember-rebuild --type=hostgroup

Логирование событий безопасности

При обращении к бэкенду ALD Pro продукт генерирует события безопасности, которые  сохраняются в файл “/var/log/aldpro/mp-core/audit.log”. Ниже покажу, как в этом журнале регистрируются типовые операции по работе с объектами типа «Пользователь»:

  • Создание пользователя usov-av:

[2026-02-08 16:37:42,452] 192.168.32.65 (admin@ALDPRO.IZPO.ME) - POST /ad/api/ds/users

[2026-02-08 16:37:42,697] 192.168.32.65 (admin@ALDPRO.IZPO.ME) - GET /ad/api/ds/users/usov-av

  • Изменения атрибутов пользователя usov-av:

[2026-02-08 16:38:37,265] 192.168.32.65 (admin@ALDPRO.IZPO.ME) - PATCH /ad/api/ds/users/usov-av

  • Изменения прав доступа пользователя usov-av (добавление в группу):

[2026-02-08 16:41:05,346] 192.168.32.65 (admin@ALDPRO.IZPO.ME) - PATCH /ad/api/ds/users/usov-av/user-groups

  • Удаление пользователя usov-av:

[2026-02-08 16:39:08,740] 192.168.32.65 (admin@ALDPRO.IZPO.ME) - DELETE /ad/api/ds/users/usov-av

Каждый тип событий журналируется, но имеет ограниченное описание по атрибутам. Например, при изменении атрибута пользователя видно, кто внес изменения (admin@ALDPRO.IZPO.ME), но не совсем понятно, что именно он изменил.

Тем не менее, для службы dirsrv (389 Directory Srever) можно включить расширенный аудит запросов к LDAP-каталогу с помощью команды:

sudo dsconf -D "cn=Directory Manager" -W ldap://<FQDN контроллера домена> config replace nsslapd-auditlog-logging-enabled=on

Потребуется предоставить пароль от УЗ «Directory Manager». 

Directory Manager — это суперпользователь для службы каталогов dirsrv. Он имеет полный доступ ко всем данным и конфигурации LDAP-каталога. Можно провести аналогию Directory Manager с пользователем root в Linux, поскольку он обладает высочайшими привилегиями и может выполнять любые операции в службе каталогов. Пароль от данного пользователя при развертывании ALD Pro аналогичен первому паролю от УЗ «admin». Пароль от этой УЗ хранится на контроллере локально, его можно сбросить на контроллере домена с помощью команды:

sudo dsconf -<имя домена через тире> directory_manager password_change

После включения журналов аудита dirsrv на контроллере домена операции по изменению объектов службы каталогов начнут журналироваться в файле «cat /var/log/dirsrv/slapd-<имя домена через тире>/audit». Информации здесь будет больше, чем в журналах ALD Pro, а самое главное, вы не пропустите никакие изменения, даже если они были внесены напрямую через LDAP или через API от FreeIPA. Ниже показано событие изменения объекта типа «Пользователь»:

time: 20260208204414
dn: uid=usov-av,cn=users,cn=accounts,dc=aldpro,dc=izpo,dc=me
#sn: Usov
result: 0
changetype: modify
delete: mail
mail: usov-av@aldpro123.izpo.me
-
add: mail
mail: usov-av@aldpro.izpo.me
-
replace: modifiersname
modifiersname: uid=admin,cn=users,cn=accounts,dc=aldpro,dc=izpo,dc=me
-
replace: modifytimestamp
modifytimestamp: 20260208174414Z
-
replace: entryusn
entryusn: 1470206
-

При изменении объекта появляется информация о конкретных атрибутах, которые были изменены, и их исходных и целевых значениях. В моём случае изменился атрибут почты «mail», было – «usov-av@aldpro123.izpo.me», стало – «usov-av@aldpro.izpo.me».

Тем не менее, данный журнал тоже имеет недостатки: в отличие от стандартного лога аудита ALD Pro в нем отсутствует исходный IP-адрес, с которого производится изменение в каталоге. Опыт показывает, что именно эта информация может быть очень важной  для администраторов ИБ, например, в случае расследования инцидента. Поэтому лучше совмещать информацию из обоих журналов для получения более полного описания события.

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