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

Puppet: ein NTP-Modul mit zwei Neuerungen

Nach und nach alle Dienste mit puppet abzudecken und dabei in der Komplexitaet immer weiter steigern. Das mache ich gerade. Heute ein Modul mit zwei Neuerungen:

  1. Sicherstellen, dass der Service auch laeuft
  2. Im Template ein Array durchlaufen

Man kann mit puppet sehr einfach sicherstellen, dass Dienste auch laufen. Dafuer reichen die drei Zeilen:

  service { 'ntp':
    ensure => running,
  }

Weiter gibt es Sinn mehrere NTP Server in der ntp.conf zu definieren. Aus diesem Grund konnte ich nicht mit der gleichen Syntax arbeiten wie bei dem apt Modul. Der entscheidende neue Abschnitt im Template ist:

<% @servers.each do |server| -%>
server <%= server %>
<% end -%>

Siehe dazu auch die Sektion Iteration in der Using Puppet Templates Dokumentation. Komplett sieht das ganze so aus:

  • Ordnerstruktur:
    ├── manifests
    │   └── init.pp
    └── templates
        └── ntp.conf.erb
  • init.pp:
    # make sure ntp package is installed and service
    # is running
     
    class ntp ( $servers = [
      '0.debian.pool.ntp.org',
      '1.debian.pool.ntp.org',
      '2.debian.pool.ntp.org',
      '3.debian.pool.ntp.org',
      ] ) {
     
      package { 'ntp':
        ensure => installed
      }
     
      service { 'ntp':
        ensure => running,
      }
     
      file { '/etc/ntp.conf':
        ensure  => 'present',
        owner   => 'root',
        group   => 'root',
        mode    => '0644',
        content => template('ntp/ntp.conf.erb'),
        }
    }
  • ntp.conf.erb:
    #############################################################################
    #                                                                           #
    # !!! This file is managed by puppet, all manual changes will be lost !!!   #
    #                                                                           #
    #############################################################################
     
     
    # Keep ntpd from panicking in the event of a large clock skew
    # when a VM guest is suspended and resumed.
    tinker panic 0
     
    # Local users may interrogate the ntp server more closely.
    restrict 127.0.0.1
    restrict ::1
     
    # You do need to talk to an NTP server or two (or three).
    <% @servers.each do |server| -%>
    server <%= server %>
    <% end -%>
     
    # Undisciplined Local Clock. This is a fake driver intended for backup
    # and when no outside source of synchronized time is available. 
    server  127.127.1.0
    fudge   127.127.1.0 stratum 10
    restrict 127.127.1.0
     
    # By default, exchange time with everybody, but don't allow configuration.
    restrict -4 default kod notrap nomodify nopeer noquery
    restrict -6 default kod notrap nomodify nopeer noquery
     
    # Driftfile.
    driftfile /var/lib/ntp/drift
  • Definition des nodes:
    node 'node1.example.org' inherits default {
      class { 'ntp':
        servers => [ 'ntp1.example.org', 'ntp2.example.org', 'ntp3.example.org' ],
      }
    }

Puppet: ein Modul mit Template, Variablen und Bedingung (/etc/apt/sources.list)

Vor kurzem erst hatte ich darueber geschrieben, wie man in puppet ein eigenes Modul definiert.
Heute moechte ich zeigen, wie in einem Modul mit Templates, Variablen und if-Bedingung gearbeitet werden kann. Ich habe mir dafuer die Datei /etc/apt/sources.list ausgesucht in die ich – je nach Host – unterschiedliche Mirror eintragen moechte. Dafuer habe ich das Verzeichnis /etc/puppet/modules/apt/ mit der folgenden Ordnerstruktur angelegt:

├── manifests
│   └── init.pp
└── templates
    └── sources.list.erb

Mein Template fuer die sources.list sieht wie folgt aus:

#############################################################################
#                                                                           #
# !!! This file is managed by puppet, all manual changes will be lost !!!   #
#                                                                           #
#############################################################################
 
 
 
deb     <%= mirror %>  <%= release %>  main
<% if source -%>
deb-src <%= mirror %>  <%= release %>  main
<% end -%>
 
deb     http://security.debian.org/  <%= release %>/updates  main
<% if source  -%>
deb-src http://security.debian.org/  <%= release %>/updates  main
<% end -%>

In dem Template kann man zwei Variablen erkennen. Diese sollen bei Bedarf ueberschrieben werden koennen <%= mirror -%> und <%= release %>. Ausserdem gibt es eine Bedingung, wenn die Variable source Wahr ist, wird auch die deb-src Zeile eingefuegt.

Die apt/manifests/init.pp fuer das Modul sieht wie folgt aus:

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

Neu ist, dass in der Klassendefinition die Variablen mit entsprechenden Standardwerten definiert wurden. Das Bild wird komplett, wenn nun ein neuer Node in der sites.pp definiert wird:

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

Hier ist neu, dass der Node „client02.example.org“ explizit angegeben wurde. Er erbt die Einstellungen des default Node. Erweiternd ist die Klasse apt definiert, bei der die Variablen mirror und source einen anderen Wert annehmen als der Standartwert.

Ich bin mir dessen bewusst, dass es ein apt Modul auf puppetforge gibt, zum Lernen finde ich das hier gerade aber einfacher.

Informationen habe ich auch aus dem Blogeintrag „Puppet templates and default values for variables„.

Puppet: Das erste eigene Modul mit einer MOTD

Nachdem die puppet Infrastruktur steht moechte ich nun auf alle Server eine eigene, individuelle MOTD nach dem Ubuntu Vorbild verteilen. Damit alles funktioniert muessen verschiedene Bedingungen erfuellt sein:

  1. Auf allen Systemen muss /etc/motd ein Symlink nach /var/run/motd sein
  2. Auf allen Systemen muss das Verzeichnis /etc/update-motd.d/ existieren
  3. Auf allen Systemen muessen die Skripte von Nick Charlton im /etc/update-motd.d/ Verzeichnis existieren
  4. Auf allen Systemen muss das Paket ‚figlet‘ installiert sein.

Die perfekte Ausgangslage um ein eigenes Modul fuer puppet zu schreiben, dass automatisch alle Bedingungen sicherstellt. #

Ich habe unter /etc/puppet/modules/ die folgende Verzeichnisstruktur erzeugt:

└── motd
    ├── files
    │   └── etc
    │       └── update-motd.d
    │           ├── 00-header
    │           ├── 10-sysinfo
    │           └── 99-footer
    └── manifests
        └── init.pp

Die Skripte im Unterverzeichnis files habe ich von der Seite Debian/Ubuntu: Dynamic MOTD. Im Verzeichnis manifests ist eine init.pp abgelegt. Diese hat den folgenden Inhalt:

class motd {
 
  package { 'figlet':
    ensure => 'installed'
  }
 
 
 
  file { '/etc/motd':
    ensure => 'link',
    target => '/var/run/motd',
  }
 
 
 
  file { '/etc/update-motd.d/':
    ensure => 'directory',
    owner  => 'root',
    group  => 'root',
    mode   => '0755'
  }
 
 
 
  file { '/etc/update-motd.d/00-header':
    ensure => 'present',
    source => 'puppet:///modules/motd/etc/update-motd.d/00-header',
    owner  => 'root',
    group  => 'root',
    mode   => '0755'
  }
 
  file { '/etc/update-motd.d/10-sysinfo':
    ensure => 'present',
    source => 'puppet:///modules/motd/etc/update-motd.d/10-sysinfo',
    owner  => 'root',
    group  => 'root',
    mode   => '0755'
  }
 
  file { '/etc/update-motd.d/99-footer':
    ensure => 'present',
    source => 'puppet:///modules/motd/etc/update-motd.d/99-footer',
    owner  => 'root',
    group  => 'root',
    mode   => '0755'
  }
}

Ich denke sie ist relativ selbsterklaerend. Als erstes wird sichergestellt, dass das Paket figlet installiert ist. Danach wird der symbolische Link erzeugt falls er nicht existiert. Als drittes validiert, dass das Verzeichnis /etc/update-motd.d/ existiert und danach die Skripte an die richtige Stelle kopiert.

Damit dieses Modul auch an alle puppet nodes verteilt wird, habe ich einen default node definiert. Dieser greift immer dann, wenn ein node nicht explizit aufgelistet wurde. Dafuer wurde die Datei /etc/puppet/manifests/site.pp mit dem folgenden Inhalt angelegt:

node default {
  class { 'motd': }
}

Nach dem manuellen Aufruf des puppet agents auf dem node wird die Konfiguration uebernommen:

user@host:~# puppet agent --server puppetmaster.example.org --test
info: Caching catalog for host.example.org
info: Applying configuration version '1398539626'
info: FileBucket adding {md5}d41d8cd98f00b204e9800998ecf8427e
info: /Stage[main]/Motd/File[/etc/motd]: Filebucketed /etc/motd to puppet with sum d41d8cd98f00b204e9800998ecf8427e
notice: /Stage[main]/Motd/File[/etc/motd]/ensure: ensure changed 'file' to 'link'
notice: /Stage[main]/Motd/File[/etc/update-motd.d/]/ensure: created
notice: /Stage[main]/Motd/File[/etc/update-motd.d/10-sysinfo]/ensure: defined content as '{md5}684e660175b367c21bbf0bf5b4cb2475'
notice: /Stage[main]/Motd/File[/etc/update-motd.d/00-header]/ensure: defined content as '{md5}13ab41fc9d68d2e2b05c31185aecfab5'
notice: /Stage[main]/Motd/File[/etc/update-motd.d/99-footer]/ensure: defined content as '{md5}8609d989ae2538fbb15f9055c995d050'
notice: Finished catalog run in 1.16 seconds
user@host:~#

Anschliessend ist die MOTD zu sehen wenn ich mich mit dem Server verbinde:

Bildschirmfoto vom 2014-04-26 21:17:22