Puppet: Klassen mit Parameteruebergabe in Hiera

Der dritte Teil beschaeft sich damit, wie man pro Node in Hiera eine eigene Konfigurationsdatei anlegen kann sowie mit Klassen denen Parameter uebergeben werden. Er baut auf den beiden vorherigen Hiera-Blogeintraegen (1 und 2) auf. Grundsaetzlich ist vorneweg zu sagen, dass das ganze mit Puppet 3 fuer eigene Module bedeutend einfacher ist, da dort Hiera und vor allem das autolookup bereits integriert ist. In Puppet 2.7 muss man fuer die gleiche Funktionalitaet eine Kruecke bauen, doch dazu spaeter mehr.

Zuerst wird die Hiera Konfiguration erweitert, so das pro Node eine eigene Konfigurationsdatei moeglich ist. Diese sollen unter hieradata/nodes/ liegen und dafuer in der die hiera.yaml die hierarchy Sektion wie folgt erweitern:

:hierarchy:
    - common
    - nodes/%{fqdn}

Danach den neuen Ordner anlegen:

 mkdir /etc/puppet/hieradata/nodes/

In Puppet 2.7 und Puppet 3 gibt es nun Unterschiede. Das haengt mit dem automatischen Parameterlookup zusammen der in Puppet 3 integriert ist, in Puppet 2.7 aber nicht. An der Hiera Konfiguration aendert sich nichts, aber die Module die ich bisher vorgestellt hatte muessen angepasst werden. Damit sich diese unter Puppet 2.7 genauso verhalten wie unter 3, muss man wie in der verlinkten Dokumentation die Parameter mit einer Hiera-Suche und Defaultparametern modifizieren.

Wie die Anpassung geht und wie man die das ganze in Hiera abbildet moechte ich im folgenden Anhand des apt-Moduls, das ich im Blogeintrag Puppet: ein Modul mit Template, Variablen und Bedingung (/etc/apt/sources.list) vorgestellt habe., zeigen.

Die init.pp wurde wie folgt geaendert:

class apt (
  $mirror  = hiera('apt::mirror', 'http://ftp.de.debian.org'),
  $release = hiera('apt::release', 'wheezy'),
  $source  = hiera('apt::source', false),
  ) {
 
  file { '/etc/apt/sources.list':
    ensure  => 'present',
    owner   => 'root',
    group   => 'root',
    mode    => '0644',
    content => template('apt/sources.list.erb'),
    }
}

Das Modul hatte ich wie folgt im Node eingebunden:

node 'client02.example.org' inherits default {
  class { 'apt':
    mirror => 'http://debianmirror.example.org/pub/linux/debian/debian/',
    source => true
  }
}

Die Einbindung erfolgt nun ueber die Datei hieradata/nodes/client02.example.org.yaml:

---
classes: apt
apt::mirror: http://debianmirror.example.org/pub/linux/debian/debian/
apt::source: true

Danach kann die komplette Nodekonfiguration aus der site.pp geloescht werden.

Puppet: Informationen des Nodes mit facter auslesen

In heterogenen Setups ist es wichtig das sich die Module und Klassen den nodespezifischen Eigenheiten anpassen. Dafuer koennen in Puppet Informationen aus facter verwendet werden.

Facter is an independent, cross-platform Ruby library designed to gather information on all the nodes you will be managing with Puppet. It is available on all platforms that Puppet is available.

Facter ist auch ein eigenstaendiges Tool. Nach dem Aufruf werden die zur Verfuegung stehenden Informationen ausgegeben.

Ein Anwendungsbeispiel ist mit einem Modul sicherzustellen, dass SSH installiert und der SSH Serverdienst auch laeuft. Auf Debian basierten Systemen heisst der Dienst ’ssh‘, waerend auf RedHat basierten Systemen der Dienst ’sshd‘ heisst. In einem Puppet Modul laesst sich das mit Informationen aus facter wie folgt abbilden:

class ssh_server {
  case $::osfamily {
    Debian: {
      $serviceName = 'ssh'
    }
    RedHat: {
      $serviceName = 'sshd'
    }
  }
 
  service { $serviceName:
    ensure => 'running',
  }

Aber nicht nur in den manifest Dateien kann man auf die Informationen aus facter zurueckgreifen, auch in den Templates stehen diese zur Verfuegung. Eine sinnvolle Modifikation des Templates jail.local.erb das ich in dem fail2ban Modul gezeigt habe ist, die Emails nicht an fail2ban@localhost sondern an fail2ban@FQDN zu verschicken. Dafuer muss die folgende Zeile geaendert werden:

  • alt:
    destemail = fail2ban@localhost
  • neu:
    destemail = fail2ban@<%= fqdn %>

Puppet: Virtuelle Ressourcen fuer Gruppen und ein Beispiel aus der Praxis

Ich habe gerade ueber virtuelle Ressourcen fuer Benutzer und public SSH-Keys geschrieben. Nach dem gleichen Schema kann man natuerlich auch virtuelle Ressourcen fuer Gruppen erzeugen. Wofuer man das ganze in der Praxis brauchen kann moechte ich an dem folgenden Beispiel erklaeren.

Das ganze basiert auf dem Beispielmodul users aus dem Beitrag Puppet: Virtuelle Ressourcen fuer Benutzer und SSH-Keys. Dort habe ich naemlich fuer den Benutzer mmuster die Gruppe wheel konfiguriert.

Mit Puppet kann ich nun in einem eigenen Modul sehr simpel sicherstellen, dass diese Gruppe auch existiert. Mein Modul groups sieht wie folgt aus:

└── manifests
    ├── init.pp
    ├── virtual.pp
    └── wheel.pp

Und auch der Aufbau der drei Dateien sind genau wie die in dem users Modul, nur viel einfacher:

  • init.pp
    class groups () {
      include groups::virtual
    }
  • virtual.pp
    class groups::virtual {
      @group { 'wheel':
        ensure => 'present',
        }
    }
  • wheel.pp
    class groups::wheel inherits groups::virtual {
      realize(
        Group['wheel']
      )
    }

Um sicherzustellen, dass die Gruppe wheel auch wirklich existiert, erweitere ich nun die Klasse mmuster.pp aus dem Beispiel des anderen Beitrags um einen include fuer die Gruppe wheel. Das ganze sieht dann wie folgt aus:

class users::mmuster inherits users::virtual {
 
  include groups::wheel
 
  realize(
    User['mmuster'],
    Ssh_authorized_key['mmuster-privat']
  )
}

Ich mache das ganze natuerlich nicht einfach nur um den Benutzer in irgendeine Gruppe zu packen, sondern die Gruppe soll auch spezielle Berechtigungen erhalten: root Rechte erlangen ohne Passworteingabe. Dieses realisiere ich ueber sudo. Dafuer habe ich ebenfalls ein eigenes Modul „sudoers“ und darin wiederum die Klasse „wheel“. Diese sieht wie folgt aus:

class sudoers::wheel {
 
  include groups::wheel
 
  file { '/etc/sudoers.d/60_wheel':
    ensure => 'present',
    source => 'puppet:///modules/sudoers/etc/sudoers.d/60_wheel',
    owner  => 'root',
    group  => 'root',
    mode   => '0440'
  }
}

Der zweite Ort an dem die Gruppe wheel konfiguriert ist. Spaetestens jetzt wuerde es zu Problemen kommen, wenn die Klasse wheel aus dem sudoers Modul UND der Nutzer mmuster zusammen auf einem Node konfiguriert sind. Mit den virtuellen Ressourcen hingegen klappt es.

Ach so, die /etc/sudoers.d/60_wheel sieht uebrigens so aus:

#############################################################################
#                                                                           #
# !!! This file is managed by puppet, all manual changes will be lost !!!   #
#                                                                           #
#############################################################################
 
#
# Members of the wheel group may gain root privileges without password
#
 
%wheel          ALL = (ALL) NOPASSWD: ALL

Puppet: Service bei Aenderung neu starten / fail2ban

Heute moechte ich mein fail2ban Modul vorstellen. Neu dabei ist, dass der Service neu gestartet wird, wenn sich eine Datei aendert. Das ist wichtig, damit die Einstellungen auch uebernommen werden.

Die Verzeichnisstruktur sieht wie folgt aus:

├── files
│   └── etc
│       └── fail2ban
│           └── action.d
│               └── sendmail-whois-lines.conf
├── manifests
│   └── init.pp
└── templates
    └── jail.local.erb

In der init.pp ist wird in dem file Abschnitt ein „notify“ in Richtung des Services aufgerufen. Das fuehrt dazu, dass wenn die Datei auf dem Node geaendert wird, auch der Service benachrichtigt wird damit dieser neu startet. Siehe dazu auch „Restart service when file changes“ aus dem Puppet CookBook. Folgendermassen sieht die init.pp aus:

# class to configure fail2ban
 
class fail2ban (
  $jails       = [],
  $sshport     = 'ssh',
  $apacheport  = 'http,https',
  $proftpdport = 'ftp,ftp-data,ftps,ftps-data',
  $postfixport = 'smtp,ssmtp',
  $dovecotport = 'smtp,ssmtp,imap2,imap3,imaps,pop3,pop3s',
  ) {
 
  package { 'fail2ban':
    ensure => installed
  }
 
  service { 'fail2ban':
    ensure => running,
    enable => true,
  }
 
  file { '/etc/fail2ban/jail.local':
    ensure  => 'present',
    content => template('fail2ban/jail.local.erb'),
    owner   => 'root',
    group   => 'root',
    mode    => '0644',
    notify  => Service['fail2ban'],
  }
 
  file { '/etc/fail2ban/action.d/sendmail-whois-lines.conf':
    ensure  => 'present',
    source  => 'puppet:///modules/fail2ban/etc/fail2ban/action.d/sendmail-whois-lines.conf',
    owner   => 'root',
    group   => 'root',
    mode    => '0644',
    notify  => Service['fail2ban'],
  }
 
}

Mein Template jail.local.erb ist wie folgt:

#############################################################################
#                                                                           #
# !!! This file is managed by puppet, all manual changes will be lost !!!   #
#                                                                           #
#############################################################################
 
 
 
#
# Local Fail2Ban configuration file.
#
 
 
[DEFAULT]
ignoreip = 127.0.0.1/8
bantime  = 600
maxretry = 3
backend = auto
destemail = fail2ban@localhost
 
 
 
#
# ACTIONS
#
 
banaction = iptables-multiport
mta = sendmail
protocol = tcp
chain = INPUT
action_mwl = %(banaction)s[name=%(__name__)s, port="%(port)s", protocol="%(protocol)s", chain="%(chain)s"]
               %(mta)s-whois-lines[name=%(__name__)s, dest="%(destemail)s", logpath=%(logpath)s, chain="%(chain)s"]
action = %(action_mwl)s
 
 
 
 
#
# JAILS
#
 
[ssh]
enabled  = <%= scope.lookupvar('fail2ban::jails').include? "ssh" %>
port     = <%= sshport %>
filter   = sshd
logpath  = /var/log/auth.log
maxretry = 6
 
 
 
[ssh-ddos]
enabled  = <%= scope.lookupvar('fail2ban::jails').include? "ssh-ddos" %>
port     = <%= sshport %>
filter   = sshd-ddos
logpath  = /var/log/auth.log
maxretry = 6
 
 
 
[apache]
enabled  = <%= scope.lookupvar('fail2ban::jails').include? "apache" %>
port     = <%= apacheport %>
filter   = apache-auth
logpath  = /var/log/apache*/*error.log
maxretry = 6
 
 
 
[proftpd]
enabled  = <%= scope.lookupvar('fail2ban::jails').include? "proftpd" %>
port     = <%= proftpdport %>
filter   = proftpd
logpath  = /var/log/proftpd/proftpd.log
maxretry = 6
 
 
 
[postfix]
enabled  = <%= scope.lookupvar('fail2ban::jails').include? "postfix" %>
port     = <%= postfixport %>
filter   = postfix
logpath  = /var/log/mail.log
 
 
 
[dovecot]
enabled = <%= scope.lookupvar('fail2ban::jails').include? "dovecot" %>
port    = <%= dovecotport %>
filter  = dovecot
logpath = /var/log/mail.log

Auch im Template gab es den scope.lookupvar Aufruf bisher nicht. Die Rueckgabe ist true oder false und so kann man nur die Dienste aktivieren, die auch manuell definiert wurden. Weitere Informationen gibt es in der Puppetlabs Doku unter Out-of-Scope Variables.

Definiert wird das ganze dann in der sites.pp wie folgt:

node 'node1.example.org' inherits default {
  class { 'fail2ban':
    jails   => [ 'ssh', 'ssh-ddos', 'apache' ],
    sshport => '2222',
  }
}

Zu der sendmail-whois-lines.conf gibts noch nen separaten Blogeintrag