Puppet : server, DB et agent

Update 05/12/22 : ajout de précisions et infos complémentaires

Bonjour,

Aujourd'hui, un billet portant sur la mise en place de la stack puppet complète : server, puppetdb, puppetboard et agent en v7.

L'installation de cette stack est réalisée avec FreeBSD 13.1 et bhyve.

Puppet server

Installation et activation de la partie puppet server :

pkg install puppetserver7
sysrc -f /etc/rc.conf puppetserver_enable="YES"

Puppet server a besoin de certains points de montages :

mount -t fdescfs fdesc /dev/fd
mount -t procfs proc /proc

À mettre dans /etc/fstab pour survivre au reboot:

fdesc   /dev/fd         fdescfs         rw      0       0
proc    /proc           procfs          rw      0       0

On peut démarrer puppet server :

service puppetserver start

Ou via cette commande pour la partie server :

[root@puppet ~]# puppet resource service puppetserver ensure=running enable=true
Notice: /Service[puppetserver]/enable: enable changed 'false' to 'true'
service { 'puppetserver':
  ensure   => 'running',
  enable   => 'true',
  provider => 'freebsd',
}

On vérifie l'écoute du service :

[root@puppet ~]# sockstat -4 | grep puppet
puppet   java       6292  49 tcp46  *:8140                *:*

À ce stade, puppet server est très basic niveau configuration.

Le fichier de log se trouve dans /var/log/puppetserver/.

Point de particularité : sous FreeBSD, redémarrer puppet sur le server redémarre le server ET l'agent.

Puppet agent

Installation de l'agent (qui est installé automatiquement en dépendance du server) :

pkg install puppet7
sysrc -f /etc/rc.conf puppet_enable="YES"

Et on démarre l'agent :

service puppet start
[root@puppet-agent /usr/local/etc]# ps aux | grep puppet
root   2899   5.0  2.9  138120  60700  -  Ss   15:52    0:00.01 /usr/local/bin/ruby30 /usr/local/bin/puppet agent --rundir=/var/run/puppet

Si l'agent est sur un server différent du puppet server, il faut faire une demande de signature de certificat. Si l'agent est sur le puppet server alors le certificat est déjà présent (généré par le serveur).

Sur l'agent :

[root@puppet-agent ~]# puppet agent --test --waitforcert 10
Info: Creating a new RSA SSL key for puppet-agent.adm.securmail.fr
Info: csr_attributes file loading from /usr/local/etc/puppet/csr_attributes.yaml
Info: Creating a new SSL certificate request for puppet-agent.adm.securmail.fr
Info: Certificate Request fingerprint (SHA256): 8C:48:64:91:2C:5B:A0:32:FE:A9:9C:F6:2C:1E:C8:84:6C:F7:59:02:20:4B:39:2C:72:E6:9D:32:01:8E:5D:D3
Info: Certificate for puppet-agent.adm.securmail.fr has not been signed yet
Couldn't fetch certificate from CA server; you might still need to sign this agent's certificate (puppet-agent.adm.securmail.fr).
Info: Will try again in 10 seconds.

Sur le server, on vérifie que la demande de certificat est bien présente :

[root@puppet ~]# puppetserver ca list --all
Requested Certificates:
    puppet-agent.adm.securmail.fr       (SHA256)  8C:48:64:91:2C:5B:A0:32:FE:A9:9C:F6:2C:1E:C8:84:6C:F7:59:02:20:4B:39:2C:72:E6:9D:32:01:8E:5D:D3
Signed Certificates:
    puppet.adm.securmail.fr       (SHA256)  4B:43:7B:F7:99:F9:BA:58:FB:4F:98:14:F1:D1:B0:20:6D:51:5C:61:D9:4D:6F:C6:6C:8E:1A:0E:5A:2B:DD:0B     alt names: ["DNS:puppet", "DNS:puppet.adm.securmail.fr"]        authorization extensions: [pp_cli_auth: true]

La demande du puppet-agent est en attente.

On valide le certificat :

[root@puppet ~]# puppetserver ca sign --certname puppet-agent.adm.securmail.fr
Successfully signed certificate request for puppet-agent.adm.securmail.fr
[root@puppet ~]# puppetserver ca list --all
Signed Certificates:
    puppet-agent.adm.securmail.fr       (SHA256)  26:06:84:7F:CB:DC:FF:73:80:E1:FF:B7:0E:CB:DD:1F:C2:83:17:08:3D:69:57:D9:FE:95:B0:6D:E8:7E:6B:EA       alt names: ["DNS:puppet-agent.adm.securmail.fr"]
    puppet.adm.securmail.fr             (SHA256)  4B:43:7B:F7:99:F9:BA:58:FB:4F:98:14:F1:D1:B0:20:6D:51:5C:61:D9:4D:6F:C6:6C:8E:1A:0E:5A:2B:DD:0B       alt names: ["DNS:puppet", "DNS:puppet.adm.securmail.fr"]  authorization extensions: [pp_cli_auth: true]

Et l'agent a bien accès au serveur :

Info: csr_attributes file loading from /usr/local/etc/puppet/csr_attributes.yaml
Info: Creating a new SSL certificate request for puppet-agent.adm.securmail.fr
Info: Certificate Request fingerprint (SHA256): 8C:48:64:91:2C:5B:A0:32:FE:A9:9C:F6:2C:1E:C8:84:6C:F7:59:02:20:4B:39:2C:72:E6:9D:32:01:8E:5D:D3
Info: Downloaded certificate for puppet-agent.adm.securmail.fr from https://puppet:8140/puppet-ca/v1
Info: Using environment 'production'
Info: Retrieving pluginfacts
Notice: /File[/var/puppet/facts.d]/group: group changed 'puppet' to 'wheel'
Info: Retrieving plugin
Notice: /File[/var/puppet/lib]/group: group changed 'puppet' to 'wheel'
Info: Caching catalog for puppet-agent.adm.securmail.fr
Info: Applying configuration version '1657008021'
Notice: /Stage[main]/Main/File[/etc/motd]/ensure: defined content as '{sha256}c9ec3add1b610dc694ea6733c458a1f4787168ec152971bbf7507127c37403e9'
Info: Creating state file /var/puppet/state/state.yaml
Notice: Applied catalog in 0.01 seconds
[root@puppet-agent ~]# puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppet-agent.adm.securmail.fr
Info: Applying configuration version '1657008123'
Notice: Applied catalog in 0.01 seconds

Il est possible de valider automatiquement la signature des certificats en fonction des domaines voulu.
Pour cela, il faut rajouter le fichier /usr/local/etc/puppet/autosign.conf :

*.adm.securmail.fr

Ubuntu 22.04

Si vous avez un agent sur Ubuntu 22.04 :

[root@puppet-agent2 ~]# wget https://apt.puppetlabs.com/puppet7-release-jammy.deb 
[root@puppet-agent2 ~]# dpkg -i puppet7-release-jammy.deb 
[root@puppet-agent2 ~]# apt install puppet-agent

Il faut renseigner le certname et le masterserver dans /etc/puppetlabs/puppet/puppet.conf :

# This file can be used to override the default puppet settings.
# See the following links for more details on what settings are available:
# - https://puppet.com/docs/puppet/latest/config_important_settings.html
# - https://puppet.com/docs/puppet/latest/config_about_settings.html
# - https://puppet.com/docs/puppet/latest/config_file_main.html
# - https://puppet.com/docs/puppet/latest/configuration.html
[main]
certname = puppet-agent2.adm.securmail.fr
server = puppet.adm.securmail.fr

Il faut ensuite démarrer l'agent et l'enable au boot :

[root@puppet-agent2 ~]# systemctl start puppet 
[root@puppet-agent2 ~]# systemctl status puppet 
[root@puppet-agent2 ~]# systemctl enable puppet

CentOS/Redhat

Si vous avez un agent en CentOS/RedHat 8 :

[root@puppet-agent3 ~]#rpm -Uvh https://yum.puppet.com/puppet7-release-el-8.noarch.rpm
[root@puppet-agent3 ~]#dnf install -y puppet-agent

Il faut renseigner le certname et le masterserver dans /etc/puppetlabs/puppet/puppet.conf :

# This file can be used to override the default puppet settings.
# See the following links for more details on what settings are available:
# - https://puppet.com/docs/puppet/latest/config_important_settings.html
# - https://puppet.com/docs/puppet/latest/config_about_settings.html
# - https://puppet.com/docs/puppet/latest/config_file_main.html
# - https://puppet.com/docs/puppet/latest/configuration.html
[main]
certname = puppet-agent3.adm.securmail.fr
server = puppet.adm.securmail.fr

Il faut ensuite démarrer l'agent et l'enable au boot :

[root@puppet-agent3 ~]# 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',
}

Code

Le code puppet doit être dans :

/usr/local/etc/puppet/code

Il faut créer l'arborescence suivante :

mkdir -p environments/production/manifests/

Le dossier production représente la branche par défaut de puppet server. Il est possible d'en changer en créant un autre dossier avec la même structure enfant et en changeant l'environnement au niveau de l'agent (pour tester des modifications à un instant T avant de merger).

On peut tester l'arborescence en test en créant le fichier environments/production/manifests/site.pp :

file { '/etc/motd':
  ensure  => file,
  owner   => 'root',
  group   => 'wheel',
  mode    => '644',
  content => "This is ${facts['fqdn']}\n\nThis node is managed by Puppet, changes may be overwritten.\n",
}

Le motd sera mis à jour pour refléter le management by puppet. Ce changement sera appliqué sur tous les noeuds indistinctement.

Pour tester sans attendre (l'agent se déclenchant toute les 30 mins) :

[root@puppet-agent ~]# puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppet.adm.securmail.fr
Info: Applying configuration version '1656948380'
Notice: /Stage[main]/Main/File[/etc/motd]/ensure: defined content as '{sha256}8fe1e0f4f6423fffd93bc4d91784a10b7a6e5420ba7429c17a60344d6e10b8d9'
Notice: Applied catalog in 0.01 seconds

Ce qui donne sur la VM :

[root@puppet-agent ~]# cat /etc/motd
This is puppet-agent.adm.securmail.fr

This node is managed by Puppet, changes may be overwritten.

PuppetDB

Le backend DB étant postgresql, il est nécessaire de créer une extension sur la base de données dédiée à puppet :

\c puppet
CREATE EXTENSION pg_trgm;

Installation de puppetdb :

pkg install puppetdb7 puppetdb-terminus7 nginx
sysrc -f /etc/rc.conf puppetdb_enable="YES"
sysrc -f /etc/rc.conf nginx_enable="YES"

Dans /usr/local/etc/puppetdb/conf.d/database.ini, il faut configurer l'accès à la DB.

[database]

# The database address, i.e. //HOST:PORT/DATABASE_NAME
subname = //pgsql-dev.adm.securmail.fr:5432/puppetdb

# Connect as a specific user
username = puppetdb

# Use a specific password
password = MyPassword

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

Puis on démarre puppetdb :

service puppetdb start

Vérification que le service est bien en écoute :

[root@puppet /usr/local/etc/puppetdb/conf.d]# sockstat -4 | grep puppetdb
puppetdb java       1084  20 tcp4   127.0.0.1:8080        *:*
puppetdb java       1084  26 tcp46  *:8081                *:*

Et on génère la conf ssl pour puppetdb :

root@puppet:~ # /usr/local/bin/puppetdb ssl-setup 
PEM files in /usr/local/etc/puppetdb/ssl are missing, we will move them into place for you
Copying files: /var/puppet/ssl/certs/ca.pem, /var/puppet/ssl/private_keys/puppet.adm.securmail.fr.pem and /var/puppet/ssl/certs/puppet.adm.securmail.fr.pem to /usr/local/etc/puppetdb/ssl
Backing up /usr/local/etc/puppetdb/conf.d/jetty.ini to /usr/local/etc/puppetdb/conf.d/jetty.ini.bak.1656948157 before making changes
Updated default settings from package installation for ssl-host in /usr/local/etc/puppetdb/conf.d/jetty.ini.
Updated default settings from package installation for ssl-port in /usr/local/etc/puppetdb/conf.d/jetty.ini.
Updated default settings from package installation for ssl-key in /usr/local/etc/puppetdb/conf.d/jetty.ini.
Updated default settings from package installation for ssl-cert in /usr/local/etc/puppetdb/conf.d/jetty.ini.
Updated default settings from package installation for ssl-ca-cert in /usr/local/etc/puppetdb/conf.d/jetty.ini.
Warning: Could not find active client-auth setting in /usr/local/etc/puppetdb/conf.d/jetty.ini. Include that setting yourself manually. Or force with puppetdb ssl-setup -f.

On peut voir la création des tables :

puppetdb=# \dt
                   List of relations
 Schema |           Name            | Type  |  Owner   
--------+---------------------------+-------+----------
 public | catalog_inputs            | table | puppetdb
 public | catalog_resources         | table | puppetdb
 public | catalogs                  | table | puppetdb
 public | certname_fact_expiration  | table | puppetdb
 public | certname_packages         | table | puppetdb
 public | certnames                 | table | puppetdb
 public | edges                     | table | puppetdb
 public | environments              | table | puppetdb
 public | fact_paths                | table | puppetdb
 public | factsets                  | table | puppetdb
 public | packages                  | table | puppetdb
 public | producers                 | table | puppetdb
 public | report_statuses           | table | puppetdb
 public | reports                   | table | puppetdb
 public | reports_20220630z         | table | puppetdb
 public | reports_20220701z         | table | puppetdb
 public | reports_20220702z         | table | puppetdb
 public | reports_20220703z         | table | puppetdb
 public | reports_20220704z         | table | puppetdb
 public | reports_20220705z         | table | puppetdb
 public | reports_20220706z         | table | puppetdb
 public | reports_20220707z         | table | puppetdb
 public | resource_events           | table | puppetdb
 public | resource_events_20220630z | table | puppetdb
 public | resource_events_20220701z | table | puppetdb
 public | resource_events_20220702z | table | puppetdb
 public | resource_events_20220703z | table | puppetdb
 public | resource_events_20220704z | table | puppetdb
 public | resource_events_20220705z | table | puppetdb
 public | resource_events_20220706z | table | puppetdb
 public | resource_events_20220707z | table | puppetdb
 public | resource_params           | table | puppetdb
 public | resource_params_cache     | table | puppetdb
 public | schema_migrations         | table | puppetdb
 public | value_types               | table | puppetdb
 public | workspace_memberships     | table | puppetdb
 public | workspaces                | table | puppetdb
(37 rows)

Il faut ensuite configurer puppetserver pour lui dire de stocker les données dans puppetdb via le fichier /usr/local/etc/puppet/puppet.conf. On en profite pour activer les rapports qui seront utilisés par le board :

[master]
report = true
reports = store, puppetdb
storeconfigs = true
storeconfigs_backend = puppetdb
resubmit_facts = true
environmentpath=/usr/local/etc/puppet/code/environments

Puis dire quel serveur puppetdb il faut utiliser, il faut modifier ce fichier /usr/local/etc/puppet/puppetdb.conf :

[main]
  server_urls = https://puppet.adm.securmail.fr:8081

Dans /usr/local/etc/puppet/routes.yaml :

---
master:
  facts:
    terminus: puppetdb
    cache: yaml

Attention! ces fichiers doivent avoir comme propriétaire puppet :

-rw-r--r--  1 puppet  puppet    217 Jul  6 16:41 puppet.conf
-rw-r--r--  1 puppet  puppet     58 Jul  6 16:37 puppetdb.conf
-rw-r--r--  1 puppet  puppet     60 Jul  5 15:20 routes.yaml

Pour que ces modifitcations soient prises en compte, il faut redémarrer puppet server :

root@puppet:~ # service puppetserver restart

On peut vérifier que la remontée d'information se fait bien directement en DB :

puppetdb=# SELECT * FROM certnames;
 id |           certname            | latest_report_id | deactivated | expired | package_hash |  latest_report_timestamp   | catalog_inputs_timestamp | catalog_inputs_uuid | catalog_inputs_hash 
----+-------------------------------+------------------+-------------+---------+--------------+----------------------------+--------------------------+---------------------+---------------------
  3 | puppet-agent.adm.securmail.fr |               90 |             |         |              | 2022-07-06 19:34:26.859+02 |                          |                     | 
  1 | puppet.adm.securmail.fr       |               91 |             |         |              | 2022-07-06 19:41:32.828+02 |                          |                     | 
(2 rows)

Afin de pouvoir accéder à la DB, il faut créer un vhost nginx pour cela :

server {
    listen      80;
    server_name puppet.adm.securmail.fr;
    charset     utf-8;

    location /pdb/ {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://127.0.0.1:8080;
        proxy_read_timeout 65;
    }
}

On peut également vérifier que l'on accède bien aux infos stockés dans puppetdb :

curl -X GET puppet.adm.securmail.fr/db/pdb/query/v4/reports -d 'query=["=", "certname", "puppet-agent.adm.securmail.fr"]' | jq
curl -X GET puppet.adm.securmail.fr/db/pdb/query/v4/facts -d 'query=["=", "certname", "puppet-agent.adm.securmail.fr"]' | jq

Puppet dashboard

Pour avoir une visualisation plus graphique et user friendly, il existe un dashboard en python :

pkg install uwsgi-py39 py39-puppetboard py39-importlib-metadata

Le package uwsgi est la pour pouvoir gérer le démarrage/arrêt du dashboard en python.

Le fichier de configuration du dashboard est /usr/local/etc/puppetboard/settings.py :

# PuppetBoard configuration file
#
# You can tune PuppetBoard by editing this file and running PuppetBoard with
# the environment variable PUPPETBOARD_SETTINGS containing the path to this
# file.
#
# Please refer to the following URL for a list of available variables:
# https://github.com/voxpupuli/puppetboard#app-settings
#
# If you are not accessing PuppetDB locally, set the following variables:
#
PUPPETDB_HOST = '127.0.0.1'
PUPPETDB_PORT = 8080
#
# If you access PuppetDB over TLS, configure the path to the TLS certifcate,
# key and CA certificate bellow:
#
# PUPPETDB_CERT = ''
# PUPPETDB_KEY = ''
# PUPPETDB_SSL_VERIFY = ''
ENABLE_CATALOG = True

DEFAULT_ENVIRONMENT = 'production'
UNRESPONSIVE_HOURS = 2
ENABLE_QUERY = True
# Uncomment to restrict the enabled PuppetDB endpoints in the query page.
# ENABLED_QUERY_ENDPOINTS = ['facts', 'nodes']
LOCALISE_TIMESTAMP = True
LOGLEVEL = 'info'
NORMAL_TABLE_COUNT = 100
LITTLE_TABLE_COUNT = 10
TABLE_COUNT_SELECTOR = [10, 20, 50, 100, 500]
DISPLAYED_METRICS = ['resources.total',
                     'events.failure',
                     'events.success',
                     'resources.skipped',
                     'events.noop']
OFFLINE_MODE = False
OVERVIEW_FILTER = None
PAGE_TITLE = "Puppet Securmail"
GRAPH_TYPE = 'pie'
GRAPH_FACTS = ['architecture',
               'clientversion',
               'domain',
               'lsbcodename',
               'lsbdistcodename',
               'lsbdistid',
               'lsbdistrelease',
               'lsbmajdistrelease',
               'netmask',
               'osfamily',
               'puppetversion',
               'processorcount']
INVENTORY_FACTS = [('Hostname', 'fqdn'),
                   ('IP Address', 'ipaddress'),
                   ('OS', 'lsbdistdescription'),
                   ('Architecture', 'hardwaremodel'),
                   ('Kernel Version', 'kernelrelease'),
                   ('Puppet Version', 'puppetversion'), ]
REFRESH_RATE = 30
DAILY_REPORTS_CHART_ENABLED = True
DAILY_REPORTS_CHART_DAYS = 8
WITH_EVENT_NUMBERS = True

SHOW_ERROR_AS = 'friendly'  # or 'raw'
CODE_PREFIX_TO_REMOVE = '/usr/local/etc/puppet/code/environments'

On va ensuite créer un dossier qui va recevoir le script uwsgi :

mkdir -p /var/www/html/puppetboard

Il faut ensuite créer le script uwsgi dans /var/www/html/puppetboard/wsgi.py :

from __future__ import absolute_import
import os

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

On adapte les droits :

chown www:www /var/www/html/puppetboard/wsgi.py

Pour le lancer manuellement :

sudo -u www bash -c 'uwsgi --socket :9090 --wsgi-file /var/www/html/puppetboard/wsgi.py'

Ou passer par un fichier .ini :

mkdir /usr/local/etc/uwsgi
nano /usr/local/etc/uwsgi/uwsgi.ini
[uwsgi]
socket = 127.0.0.1:9090
wsgi-file = /var/www/html/puppetboard/wsgi.py
workers = 1

Puis pour le lancer :

uwsgi /usr/local/etc/uwsgi/uwsgi.ini

Ces deux méthodes ne sont que temporaires.

Pour que ça soit effectif au boot, il faut passer par la création du fichier .ini et rajouter à /etc/rc.conf :

sysrc -f /etc/rc.conf uwsgi_enable="YES"
sysrc -f /etc/rc.conf uwsgi_configfile="/usr/local/etc/uwsgi/uwsgi.ini"

Pour pouvoir accéder au dashboard, il faut modifier le vhost nginx :

upstream puppetboard {
    server 127.0.0.1:9090;
}

server {
    listen      80;
    server_name puppet.adm.securmail.fr;
    charset     utf-8;

    location /pdb/ {
        proxy_set_header Host $http_host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://127.0.0.1:8080;
        proxy_read_timeout 65;
    }

    location /static {
        alias /usr/local/lib/python3.9/site-packages/puppetboard/static;
    }

    location / {
        uwsgi_pass puppetboard;
        include    /usr/local/etc/nginx/uwsgi_params;
    }
}

Pour accéder au menu metrics du board, il faut éditer le fichier /usr/local/etc/puppetdb/conf.d/auth.conf :

+         {
+            # Allow puppetdashboard to access the metrics service
+            match-request: {
+                path: "/metrics"
+                type: path
+                method: get
+            }
+            allow-unauthenticated: true
+            sort-order: 500
+            name: "unauth puppetlabs puppetdb metrics for puppetdashboard"
+        },
         {
             # Allow nodes to access the metrics service
             # for puppetdb, the metrics service is the only
             # service using the authentication service
             match-request: {
                 path: "/metrics"
                 type: path
-                method: [get,post]
+                method: post
             }
             allow: "*"
             sort-order: 500

Si tout s'est bien déroulé, la stack puppet devrait être fonctionnelle.

Noop

Il est possible de passer l'agent puppet en mode noop, c'est à dire que l'agent puppet va continuer à être lancé mais aucun changement ne sera appliqué.

[root@puppet-agent /usr/local/etc/puppet]# puppet agent -t --noop
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Applying configuration version '1667134974'
Notice: Applied catalog in 0.01 seconds

L'état est visible sur le dashboard.

Pour le remettre en actif, il suffit de repasser la même commande mais sans le --noop :

[root@puppet-agent ~]# puppet agent -t
Info: Using environment 'production'
Info: Retrieving pluginfacts
Info: Retrieving plugin
Info: Caching catalog for puppet-agent.adm.securmail.fr
Info: Applying configuration version '1667135661'
Notice: Applied catalog in 0.01 seconds

Normalement en l'état, vous avez une stack puppet fonctionnelle. Il restera à gérer les modules et les hiera.

Have fun.