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

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

Для управления конфигурациями устройств в своей организации я выбрал Puppet.

Puppet сам по себе – отличный инструмент для управления небольшим количеством серверов. С помощью puppet DSL мы можем описать и сконфигурировать практически любую задачу. Вот здесь есть хорошая статья о puppet – Введение в Puppet.

Недостаток чистого Puppet – это способ выбора ноды (цели) для конфигурирования. Каждый манифест конфигурации начинается с селектора цели, к которой он будет применён. Цель можно описать точно по имени хоста, регулярным выражением или ключевым словом default – для всех остальных. Проблема в том, что для каждой ноды будет выбран только один, наиболее специфичный манифест. Мы не можем, например, описать некие общие правила для всех хостов в манифесте default, а затем добавить к этим правилам некоторые корректировки только нужным компьютерам. С таким поведением можно мирится, если нужно управлять небольшим количеством нод, но если их становится больше, то нужно что-то делать.

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

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

Для гибкой настройки конфигураций в Puppet применяется специальный механизм – внешний классификатор узлов (ENC). Если сильно упростить, то внешний классификатор – это программа или скрипт, принимающий в качестве параметра имя компьютера, и отдающая через стандартный вывод конфигурацию, описанную в формате yaml.

Combenc

Воспользовавшись этим механизмом мною на python в муках был рождён внешний классификатор, названный Combenc. Механизм работы связки Puppet + Combenc можно описать примерно так:

  1. Компьютер, находящийся в домене, периодически запрашивает у puppet-сервера конфигурацию.

  2. Сервер запускает Combenc, передавая в качестве параметра имя компьютера.

  3. Combenc получает из Active Directory список групп безопасности, в которых состоит компьютер и пользователь. Обычно в группы включается пользователи, но так как Puppet управляет только хостами, мне был нужен способ привязать пользователя к компьютеру. Для связывания я заполняю атрибут Управляется у объекта компьютера в Active Directory.

  4. Далее классификатор читает:

    1. файл конфигурации, задающий правила для всех нод;

    2. файлы конфигураций, соответствующие именам групп безопасности;

    3. файл, соответствующий имени компьютера.

  5. Затем Combenc комбинирует файлы конфигураций.  Файлы конфигурации представляют собой yaml файлы, состоящие из структуры с именами классов и применяемых к классам параметров. Кроме своих параметров классы могут обладать булевыми параметрами merge и sealed. При использовании параметра merge Combenc будет пытаться объединить списки и словари конфигураций. При использовании параметра sealed класс будет «запечатан» и не будет переопределён, если встретится далее в файлах конфигураций.

  6. В результате полученный конфиг применяется к компьютеру.

Как этим воспользоваться

Я предполагаю, что у Вас уже настроен puppet-сервер, к которому подключено некоторое количество нод.

  1. Сперва разместим классификатор на сервере с Puppet. Создадим папку /opt/combenc/ и расположим в ней скрипты Combenc:

    mkdir /opt/combenc
    cd /opt/combenc
    git clone https://github.com/nsuslov/combenc.git .
  2. После скачивания не забываем дать права на запуск:

    chmod +x /opt/combenc/combenc.py
  3. Далее необходимо заполнить конфигурационный файл config.yaml:

    ldap:
      uri: 'ldap://ns01.example.com'
      user: 'CN=puppet,CN=Users,DC=example,DC=com'
      cred: 'Pa$$word'
      base_dn: 'OU=00-Unit,DC=example,DC=com'
    rules:
      folder: '/etc/puppetlabs/code/environments'
    environments:
      dev:
        - my-pc.example.com
      test:
        - 00-pc-01.example.com
        - 00-pc-02.example.com
    • ldap.uri – адрес одного из наших контроллеров домена;

    • ldap.user, user.cred – уникальное имя (dn) и пароль пользователя, которым мы будем забирать данные с ldap-сервера;

    • ldap.base_dn – начальная точка для поиска компьютеров. Позволяет ограничить поиск определённым организационным юнитом;

    • rules.folder – это каталог, в котором будут располагаться окружения. Я предпочитаю хранить в окружении вместе папку modules – описание логики применяемых конфигураций и combenc – описания конфигураций с параметрами;

    • environments – здесь могут быть словари с названием окружения и массивом нод, которые в него входят. Компьютеры, для которых не указано специфическое окружение, работают в окружении production. Я предпочитаю схему с тремя окружениями: development – моё окружение для разработки, testing – небольшая группа компьютеров для тестирования конфигураций перед внедрением в прод, production – все остальные.

  4. Затем нужно настроить сервер puppet для использования Combenc. Для этого в конфиге /etc/puppetlabs/puppet/puppet.conf нужно добавить следующие строки и перезапустить сервис puppetserver:

    [master]
    node_terminus = exec
    external_nodes = /opt/combenc/combenc.py

В целом на этом настройка закончена. Теперь можно написать какой-нибудь модуль и применить его.

Пример модуля Puppet

Предположим, у нас есть следующая задача: мы хотим дать возможность пользователям группы domain-admins подключаться по ssh ко всем компьютерам, а пользователям группы 01-unit-admins подключаться к компьютерам, входящим в группу 01-computers.

  1. Создадим в /etc/puppetlabs/code/environments/production/modules каталог sshd.

    mkdir -p /etc/puppetlabs/code/environments/production/modules/sshd
  2. Перейдём в него и создадим нужную нам структуру папок:

    cd /etc/puppetlabs/code/environments/production/modules/sshd
    mkdir {manifests,templates}
  3. Теперь создадим файл manifests/init.pp со следующим содержимым:

    # Class: sshd
    #
    # Настройка sshd
    #
    # @param groups Группы, имеющие право подключаться по ssh
    #
    # @param merge Объединить правила. Combenc объединит список, 
    #        если правило встретится ниже
    # @param sealed Запечатать правило. Combenc не переопределит это правило, 
    #        если оно встретится ниже
    #
    class sshd (
      Array[String] $groups,
    
      Boolean $merge = false,
      Boolean $sealed = false,
    ) {
      $groups_line = join($groups, ' ')
    
      file { 'sshd config':
        path    => '/etc/ssh/sshd_config.d/10-puppet.conf',
        content => template('sshd/puppet.erb'),
      }
    
      service { 'sshd service':
        ensure    => running,
        name      => 'sshd',
        enable    => true,
        subscribe => File['sshd config'],
      }
    }

    Этот класс создаст файл /etc/ssh/sshd_config.d/10-puppet.conf по шаблону и перезапустит сервис sshd, если файл 10-puppet.conf был изменен.

  4. Добавим шаблон templates/puppet.erb:

    # Puppet sshd
    # DONT EDIT THIS FILE
    
    <% if (@groups_line) -%>
    AllowGroups <%= @groups_line %>
    <% end -%>

Модуль готов. Теперь напишем правила, согласно которым этот класс будет применяться к компьютерам.

Пример правила Combenc

  1. Создадим каталог с правилами – /etc/puppetlabs/code/environments/production/combenc

    mkdir -p /etc/puppetlabs/code/environments/production/combenc
  2. Затем перейдём в него и создадим нужные папки: groups – для конфигураций групп безопасности и hosts – для конфигураций, применяемых к конкретным хостам.

    cd /etc/puppetlabs/code/environments/production/combenc
    mkdir {groups,hosts}
  3. Создадим общее правило для всех хостов defautl.yaml со следующим содержимым:

    sshd:
      merge: true
      groups:
        - domain-admins
  4. Теперь создадим правило для группы компьютеров groups/01-computers.yaml:

    sshd:
      groups:
        - 01-unit-admins
  5. Предположим, мы хотим дать возможность подключаться к компьютеру 01-pc-34 ещё и локальному пользователю user. Создадим файл hosts/01-pc-34.example.com.yaml со следующим содержимым:

    sshd:
      groups:
        - user

В результате Combenc должен сформировать конфиг для компьютеров объединив правила. Убедимся в этом вызвав скрипт и предав ему dns имя компьютера в качестве параметра:

/opt/combenc/combenc.py 01-pc-34.example.com

Команда вернёт следующий вывод:

classes:
  sshd:
    groups:
    - domain-admins
    - 01-unit-admins
    - user
environment: production

При следующем обращении компьютеров к puppet-серверу эта конфигурация будет применена. 

В результате мы должны добавить такую структуру файлов:

├── combenc
│   ├── default.yaml
│   ├── groups
│   │   └── 01-computers.yaml
│   └── hosts
│       ├── 01-pc-34.example.com.yaml
└── modules
    └── sshd
        ├── manifests
        │   └── init.pp
        └── templates
            └── puppet.erb

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

Спасибо за внимание! Жду предложения, идеи, критику и прочую обратную связь в комментариях. 

Репозиторий Сombenc

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


  1. silver_shine
    20.06.2023 09:49

    Почему бы не использовать Foreman?

    https://www.theforeman.org/


    1. nwort Автор
      20.06.2023 09:49

      У меня сейчас нет сервера с foreman под рукой и мой ответ будет основан на беглом чтении документации. Поправьте меня, если я не прав, но на сколько я понял, foreman не умеет выбирать группы безопасности ldap в качестве целей для применения классов.


      1. WarFair
        20.06.2023 09:49

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


  1. Habr1337
    20.06.2023 09:49

    при переходе на отечественную операционную систему

    Combenc получает из Active Directory список

    какой-то полупроводниковый полупереход :)

    я думал теперь ALD+ansible в моде


    1. nwort Автор
      20.06.2023 09:49

      Мы пытаемся постепенно интегрировать этот отечественный софт в существующую инфраструктуру. Кроме того, мы переходим на RedOS, так что с ALD не судьба.


  1. WarFair
    20.06.2023 09:49

    Я как-то тоже раздумывал, что можно сделать с линуксовыми тачками в формате применения групповых политик - просто теоретические размышления - если кто-то сделает тулзу (а может и уже сделал) для линукса, которая будет выдавать результат в формате gpresult для хоста и юзера, с возможностью для каждого параметра из политик выполнять соответствующий заданный скрипт (данный параметр применяется скриптом gp_param_1.sh), то можно было бы тем же паппетом поддерживать в актуальном состоянии эти самые скрипты, а параметрами рулить из ад - условно в крон и/или автозапуск добавляем задачу на применение политик для хоста (чтоб исполнялось от рута), а в автостарт юзеру добавлялся скрипт, который уже от имени юзера будет задавать настройки в соответствии с политиками для юзера - для чего скрипт придумали, то по сути реализовали для поддержки, для чего не придумали, ну значит не поддерживается...

    Опять же, размышления просто теоретические, просто потому что как итог отказ от ad в сторону freeipa и его аналогов (ald от астры например) это тоже входит в план импортозамещения, и изобретать временные костыли может оказаться немного бесполезным