Привет всем,

Продолжаем практиковаться с puppet, и в рамках этой заметки дополним нашу инфраструктуру новыми компонентами - PuppetDB и Dashboard.

Настройка PuppetDB

PuppetDB - по сути это отдельный модуль, представляющий из себя хранилище puppet. Все данные помещаются в базу на postgresql. И в дополнение после внедрения puppetdb, нам открывается возможность взаимодействия посредством API.

В базе хранятся данные:

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

Установка PostgreSQL

Для начала настроим postgresql, и создадим новую базу данных. В документации рекомендуют устанавливать 11 версию postgresql или выше.

Подключаемся на сервер, и выбираем оптимальную версию =)

[root@puppetdb ~]# yum module list postgresql
Last metadata expiration check: 2:04:08 ago on Fri 10 Nov 2023 03:51:06 PM +06.
AlmaLinux 8 - AppStream
Name                                  Stream                            Profiles                                     Summary
postgresql                            9.6                               client, server [d]                           PostgreSQL server and client module
postgresql                            10 [d]                            client, server [d]                           PostgreSQL server and client module
postgresql                            12                                client, server [d]                           PostgreSQL server and client module
postgresql                            13                                client, server [d]                           PostgreSQL server and client module
postgresql                            15                                client, server [d]                           PostgreSQL server and client module

Hint: [d]efault, [e]nabled, [x]disabled, [i]nstalled

Как видно по умолчанию доступна 10я версия. Но мы поставим последнюю доступною версию из коробки.

[root@puppetdb ~]# yum module enable postgresql:15 -y

Ну и теперь просто ставим пакет postgresql:

[root@puppetdb ~]# yum install postgresql-server postgresql-contrib -y

Далее проинициализируем новый инстанс:

[root@puppetdb ~]# /usr/bin/postgresql-setup --initdb
 * Initializing database in '/var/lib/pgsql/data'
 * Initialized, logs are in /var/lib/pgsql/initdb_postgresql.log

Конфигурация PostgreSQL

Сразу же подредактируем конфигурацию базы. Запускать инстанс будем на локалхосте:

[root@puppetdb ~]# vim /var/lib/pgsql/data/postgresql.conf
---
listen_addresses = 'localhost'
port = 5432

И разрешим подключаться к нашей базе в pg_hba:

[root@puppetdb ~]# vim /var/lib/pgsql/data/pg_hba.conf
---
# TYPE  DATABASE        USER            ADDRESS                 METHOD
host    puppetdb        puppetdb        127.0.0.1/32            scram-sha-256

Запускаем сервис:

[root@puppetdb ~]# systemctl enable --now postgresql
Created symlink /etc/systemd/system/multi-user.target.wants/postgresql.service → /usr/lib/systemd/system/postgresql.service.

Подключаемся к базе для создания новой бд и пользователя.

[root@puppetdb ~]# sudo -u postgres psql
could not change directory to "/root": Permission denied
psql (15.3)
Type "help" for help.

postgres=#

Меняем алгоритм шифрования паролей:

postgres=# set password_encryption = "scram-sha-256";
SET

Создаем нового пользователя и базу:

[root@puppetdb ~]# sudo -u postgres createuser -DRSP puppetdb
Enter password for new role: *******
Enter it again: *******

[root@puppetdb ~]# sudo -u postgres createdb -E UTF8 -O puppetdb puppetdb

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

[root@puppetdb ~]# psql -h 127.0.0.1 -U puppetdb -d puppetdb -W
Password: *******
psql (15.3)
Type "help" for help.
puppetdb=>

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

[root@puppetdb ~]# sudo -u postgres psql puppetdb
psql (15.3)
Type "help" for help.

puppetdb=# create extension pg_trgm;
CREATE EXTENSION
puppetdb=# \q

Ставим puppet агента

Теперь займемся инсталяцией puppetdb. Сначала монтируем репозиторий puppet, для последующей установки агента.

[root@puppetdb ~]# yum install -y https://yum.puppetlabs.com/puppet8-release-el-8.noarch.rpm
[root@puppetdb ~]# yum install puppet-agent -y

Устанавливаем связь с паппет-серверов. Обновляем конфигурацию агента, и подписываем сертификат нового сервера. Обновляем конфигу:

[root@puppetdb ~]# vi /etc/puppetlabs/puppet/puppet.conf
---
[main]
server = puppetmaster.nixhub.ru
certname = puppetdb.nixhub.ru
runinterval = 30

Далее просто запускаем агента:

[root@puppetdb ~]# /opt/puppetlabs/bin/puppet resource service puppet ensure=running enable=true
Notice: /Service[puppet]/ensure: ensure changed 'stopped' to 'running'
service { 'puppet':
  ensure   => 'running',
  enable   => 'true',
  provider => 'systemd',
}

Идем на puppet-server, и подписываем сертификат для данного новой ноды:

[root@puppetmaster ~]# puppetserver ca sign --certname puppetdb.nixhub.ru
Successfully signed certificate request for puppetdb.nixhub.ru

Конфигурация puppetdb

Устанавливаем puppetdb, через агента:

[root@puppetdb ~]# /opt/puppetlabs/bin/puppet resource package puppetdb ensure=latest
Notice: /Package[puppetdb]/ensure: created
package { 'puppetdb':
  ensure   => '8.2.0-1.el8',
  provider => 'dnf',
}

После установки сервиса, нужно внести изменения в его конфигурацию.

Первостепенно подключить его к ранее настроенному инстансу postgresql. Редактируем конфигурационный файл puppetdb:

[root@puppetdb ~]# vim /etc/puppetlabs/puppetdb/conf.d/database.ini
---
[database]
# The database address, i.e. //HOST:PORT/DATABASE_NAME
subname = //localhost:5432/puppetdb

# Connect as a specific user
username = puppetdb

# Use a specific password
password = pwdpwd

# How often (in minutes) to compact the database
# gc-interval = 60

Тут раскоментируем поля subname, username, password. Значение в поле subname остается дефолтным, в случаи если у вашей базы сетевой сокет или имя базы отличается, то нужно будет подправить.

В username, password указываем креды созданного пользователя для подключения к базе.

Также можно заглянуть в настройки вебсервера тут: /etc/puppetlabs/puppetdb/conf.d/jetty.ini. По умолчанию, он настроен на 8081 ssl порту с самозаверенными сертификатами.

Запускаем утилуту, которая подготовит сертификаты:

[root@puppetdb ~]# puppetdb ssl-setup

Не забываем на фаере открыть порт:

[root@puppetdb ~]# firewall-cmd --add-port=8081/tcp --permanent
[root@puppetdb ~]# firewall-cmd --reload

Ну и запускаем сервис:

[root@puppetdb ~]# /opt/puppetlabs/bin/puppet resource service puppetdb ensure=running enable=true
Notice: /Service[puppetdb]/enable: enable changed 'false' to 'true'
service { 'puppetdb':
  ensure   => 'running',
  enable   => 'true',
  provider => 'systemd',
}

Подключение к puppetdb

Ну и последним этапом этой задачи остается прикрутить сервер puppet к puppetdb. Вновь подключаемся к серверу puppetmaster и ставим плагин puppetdb-termini. Данный плагин может быть установлем через стандарный пакентый менеджер, либо через puppet.

[root@puppetmaster ~]# yum install puppetdb-termini

Редактируем конфиг паппет сервера.

[root@puppetmaster ~]# vim /etc/puppetlabs/puppet/puppet.conf
---
[master]
...
storeconfigs = true
storeconfigs_backend = puppetdb

В контекст master добавляем директивы storeconfigs, storeconfigs_backend.

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

Создадим еще один конфиг, в котором будут содержатся настройки подключения к puppetdb:

[root@puppetmaster ~]# vim /etc/puppetlabs/puppet/puppetdb.conf
---
[main]
server_urls = http://puppetdb.nixhub.ru:8081/

В значении server_urls указывается мой поднятый адресс сервера puppetdb. Как временным решением добавил в /etc/hosts адрес и ip-сервера.

Ну и последним этапом добавляем маршрут:

[root@puppetmaster ~]# vim /etc/puppetlabs/puppet/routes.yaml
---
master:
  facts:
    terminus: puppetdb
    cache: yaml

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

[root@puppetmaster ~]# /opt/puppetlabs/bin/puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppetmaster.nixhub.ru
Info: Applying configuration version '1699798752'
Notice: /Stage[main]/Zbx_agent::Config/Exec[firewalld]/returns: executed successfully (corrective)
Notice: Applied catalog in 1.38 seconds

Если на этом этапе никаких ошибок не было, значит настройки корректны.

Теперь если в браузере обратимся к серверу по ip и порту, то откроется небольшая дэшка. В ней же мы можем увидеть, колличество активных нод. puppetdb-dashboard.png

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

Закрываем API за прокси

По причине вышеупомянутой проблемы, пойдем по второму пути и настроим Nginx. Вебсервер возьмет на себя создание tls-сессий, ограниение по ip. Ну и в дополнение, установим базовую авторизацию.

Включаем модуль с последней актуальной версией nginx,

[root@puppetdb ~]# yum module reset nginx
[root@puppetdb ~]# yum module enable nginx:1.22 -y
[root@puppetdb ~]# yum install nginx -y

Далее я привел к такому виду основной файл - nginx.conf:

[root@puppetdb ~]# vim /etc/nginx/nginx.conf
---
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log notice;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    keepalive_timeout   65;
    types_hash_max_size 4096;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    include /etc/nginx/conf.d/*.conf;

}

Создадим каталог, куда будет складывать сертификаты в последующем.

[root@puppetdb ~]# mkdir /etc/nginx/ssl

И теперь для того что бы не создавать новые серты, создадим сим.линк на сертификаты puppetdb.

[root@puppetdb ~]# ln -s /etc/puppetlabs/puppetdb/ssl/public.pem /etc/nginx/ssl/public.pem
[root@puppetdb ~]# ln -s /etc/puppetlabs/puppetdb/ssl/private.pem /etc/nginx/ssl/private.pem

На линках меняем владельца/группу:

[root@puppetdb ~]# chown -R nginx:nginx /etc/nginx/ssl/*.pem

Далее пишем конфигурацию прокси сервера для puppetdb:

[root@puppetdb ~]# vim /etc/nginx/conf.d/puppetdb.conf
---
server {
        listen 80;
        server_name     puppetdb.nixhub.ru;

        location / {
                return 301 https://puppetdb.nixhub.ru$request_uri;
        }
}

server {
        listen 443 ssl;
        server_name puppetdb.nixhub.ru;

        ssl_certificate /etc/nginx/ssl/public.pem;
        ssl_certificate_key     /etc/nginx/ssl/private.pem;

        # Log paths;
        access_log      /var/log/nginx/puppetdb-access.log;
        error_log       /var/log/nginx/puppetdb-erroe.log;

        # Set puppet master ip;
        set $puppetmaster       10.8.5.111;

        # Restricted by ip;
        allow   10.8.5.111;
        allow   10.8.5.6;
        deny    all;

        location / {
                if ($remote_addr = $puppetmaster) {
                        set $auth       off;
                }

                if ($remote_addr != $puppetmaster) {
                        set $auth "HTTP Auth";
                }

                auth_basic      $auth;
                auth_basic_user_file    .htpasswd;

                proxy_pass http://localhost:8080;
        }

}

Набросал базовый конфиг, который состоит из двух серверов. Первый запущенный на 80 порту, просто служит редиректом, который перебрасывает запросы на 443 ssl порт.

Второй сервер, который слушает 443 порт, разграничивает доступ по ip-адресам. В данном случаи я разрешаю доступ только со своего компа, и сервера puppet, остальным отказываю в доступе.

# Restricted by ip;
allow   10.8.5.111;
allow   10.8.5.6;
deny    all;

Затем в контексте location, проверяю является ли удаленный адрес клиента - адресом puppet сервера. Если является до nginx спроксирует запрос в бекенд. Иначе, удаленный клиент должен будет пройти авторизацию.

Для авторизации нужно сгенерить файлик с паролями. Ставим пакет httpd-tools для создания файла с паролями.

[root@puppetdb ~]# yum install httpd-tools

Ну и создаем файлик .htpasswd, попутно добавив пользователя:

[root@puppetdb ~]# htpasswd -c /etc/nginx/.htpasswd admin
New password: ******
Re-type new password: ******
Adding password for user admin

Наш прокси будет ссылаться на 8080 http порт запушенный локально, поэтому перенастроим pupetdb.

# Раскомментировать эти директивы
host = 127.0.0.1
port = 8080

# Закоментировать эти директивы
# ssl-host = 0.0.0.0
# ssl-port = 8081

Ну и для применения настроек, перезапустить сервис:

[root@puppetdb ~]# systemctl restart puppetdb

Не забываем открыть порт на фаере,

[root@puppetdb ~]# firewall-cmd --add-port={80,443)/tcp --permanent
[root@puppetdb ~]# firewall-cmd --reload

Запускаем сервис вебсервера:

[root@puppetdb ~]# systemctl enable --now nginx

Так как порт к API был изменен, подключаемся на puppet-master, и меняем конфигурацию:

[root@puppetmaster ~]# vim /etc/puppetlabs/puppet/puppetdb.conf
---
[main]
server_urls = https://puppetdb.nixhub.ru/

Перезапускаем сервис паппета на основном сервере, и проверяем работу агента:

[root@puppetmaster ~]# systemctl restart puppetserver

Запускаем тест агентом:

[root@puppetmaster ~]# /opt/puppetlabs/bin/puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppetmaster.nixhub.ru
Info: Applying configuration version '1699867114'
Notice: /Stage[main]/Zbx_agent::Config/Exec[firewalld]/returns: executed successfully (corrective)
Notice: Applied catalog in 1.47 seconds

Отлично все работает.

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

Настройка Puppet Dashboard

В этой главе давайте прикрутим UI Dashboard для puppetdb. По сути, это дешка дает нам возможность только для просмотра данных по хостам.

Сам сервис написан на python c использованием flask фреймворка. И для запуска проекта будем использовать uwsgi в связке уже с установленным nginx.

Ставим python, uwsgi

На основании требований в сервисе ставим python3.8:

[root@puppetdb ~]# yum install python38 python38-devel gcc

Через пакентный менеджер питона, устанавливаем uwsgi и пакет puppetboard.

[root@puppetdb ~]# pip3.8 install uwsgi puppetboard

Настраиваем puppetboard

Далее создадим вебкаталог, в котором будет лежать файлик с настройками puppetboard и конфигурация wsgi:

[root@puppetdb ~]# mkdir -p /var/www/puppetboard

Из пакета puppetboard копируем семпл с настройками сервиса.

[root@puppetdb ~]# cp -v /usr/local/lib/python3.8/site-packages/puppetboard/default_settings.py /var/www/puppetboard/settings.py
'/usr/local/lib/python3.8/site-packages/puppetboard/default_settings.py' -> '/var/www/puppetboard/settings.py'

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

/var/www/puppetboard/settings.py

Генерим новый секретный ключ для flask:

[root@puppetdb ~]# python3.8 -c 'import secrets; print(secrets.token_hex())'
f09175100257f83097254e934a06de814f8e0ae581aef08e49f4a540d8153d1d

Полученную строку вставляем в значение переменной SECRET_KEY:

[root@puppetdb ~]# vim /var/www/puppetboard/settings.py
---
SECRET_KEY = 'f09175100257f83097254e934a06de814f8e0ae581aef08e49f4a540d8153d1d'  # nosec

Пишем скриптец для запуска wsgi:

[root@puppetdb ~]# vim /var/www/puppetboard/wsgi.py
---
from __future__ import absolute_import
import os

# Needed if a settings.py file exists
os.environ['PUPPETBOARD_SETTINGS'] = '/var/www/puppetboard/settings.py'
from puppetboard.app import app as application

Теперь можно протестировать работу дешбоарда:

[root@puppetdb ~]# uwsgi --socket :8081 --wsgi-file /var/www/puppetboard/wsgi.py --enable-threads --protocol http

Сервис должен запуститься на порту 8081, в браузере обратимся к серверу: puppetdb-dashboard1.png

Из скриншота можете заметить, что хосты в статусе unreported. Видимо при настройки подключения puppet к puppetdb, я заигнорил этот поинт. Что бы исправить это, подключаемся к серверу puppetmaster и изменяем его конфиг:

[root@puppetmaster ~]# vim /etc/puppetlabs/puppet/puppet.conf
---
[master]
dns_alt_names = puppetmaster,puppetmaster.nixhub.ru
storeconfigs = true
storeconfigs_backend = puppetdb
reports = store,puppetdb       # <--- Добавил

В секцию master, добавляем поле reports со значением store,puppetdb.

И перезапускаем сервис:

[root@puppetmaster ~]# systemctl restart puppetserver

Запускаем puppetboard

В заключении остается только прикрутить сервис для запуска uwsgi. Пишем новый systemd-юнит:

[root@puppetdb ~]# vim /etc/systemd/system/uwsgi.service
---
[Unit]
Description=uWSGI Puppetboard service
After=syslog.target

[Service]
ExecStart=/usr/local/bin/uwsgi --socket :8081 --wsgi-file /var/www/puppetboard/wsgi.py --enable-threads --protocol http
Restart=always
KillSignal=SIGQUIT
Type=notify
StandardError=syslog
NotifyAccess=all

[Install]
WantedBy=multi-user.target

Перечитываем конфигурацию systemd, и запускаем сервис с uwsgi:

[root@puppetdb ~]# systemctl daemon-reload
[root@puppetdb ~]# systemctl enable --now uwsgi

Проверяем запустился ли сервис:

[root@puppetdb ~]# systemctl status uwsgi
● uwsgi.service - uWSGI Puppetboard service
   Loaded: loaded (/etc/systemd/system/uwsgi.service; enabled; vendor preset: disabled)
   Active: active (running) since Mon 2023-11-13 20:41:20 +06; 4s ago
 Main PID: 678428 (uwsgi)
   Status: "uWSGI is ready"
    Tasks: 1 (limit: 23149)
   Memory: 26.7M
   CGroup: /system.slice/uwsgi.service
           └─678428 /usr/local/bin/uwsgi --socket :8081 --wsgi-file /var/www/puppetboard/wsgi.py --enable-threads --protocol http

Копируем конфиг nginx из прошлой инсталяции, вносим изменения в него:

[root@puppetdb ~]# vim /etc/nginx/conf.d/puppetboard.conf
---
[root@puppetdb ~]# cat /etc/nginx/conf.d/puppetboard.conf
server {
	listen 80;
	server_name	puppetboard.nixhub.ru;

	location / {
		return 301 https://puppetdb.nixhub.ru$request_uri;
	}
}

server {
	listen 443 ssl;
	server_name puppetboard.nixhub.ru;

	ssl_certificate	/etc/nginx/ssl/public.pem;
	ssl_certificate_key	/etc/nginx/ssl/private.pem;

	# Log paths;
	access_log	/var/log/nginx/puppetboard-access.log;
	error_log	/var/log/nginx/puppetboard-erroe.log;


	location / {
		auth_basic	$auth;
		auth_basic_user_file	.htpasswd;

		proxy_pass http://localhost:8081;
	}

}

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

В /etc/hosts добавляем запись с именем сервера и перезапускаем nginx. В браузере открываем страничку уже по новому имени.

На этом все, мы посмотрели два сервиса, с помошью которых можем затюнить нашу puppet инфру. Стоит отметить, что в этой заметке установка puppetboard производилась из под пользователя root и вне python окружения. Также я не раскрыл тему настройки selinux и firewall под этот сетап. Для реализации этой темы в проде, стоит также проработать данные вопросы.