Gerenciando o iptables com o Puppet

10

A idéia de gerenciar nossas iptablesregras com o Puppet foi levantada. Vejo que augeastem uma iptableslente, mas atualmente é experimental.

Alguém tem alguma sugestão de como lidar com isso? Idealmente, eu gostaria de construir as cadeias com base na classe de um servidor.

Belmin Fernandez
fonte
1
Qual sabor do Linux? Alguns têm algumas coisas padrão do iptables que é uma boa idéia para se trabalhar.
freiheit 22/09
1
RHEL na minha situação.
Belmin Fernandez 22/09

Respostas:

6

Aqui está o que estou fazendo com o Red Hat Enterprise (RHEL).

O RHEL tem um iptablesserviço que carrega regras /etc/sysconfig/iptablese estou trabalhando para modificar esse arquivo e reiniciar o serviço iptables. Muitas pessoas gostam de colocar fragmentos em um diretório iptables.d e criar um conjunto de regras iptables (via make ou algo parecido) a partir dele. Eu incluo coisas para reconstruir o conjunto de regras padrão, mas isso geralmente nunca faz nada. Se suas necessidades são simples, você pode simplesmente copiar um arquivo iptables para o sistema.

Apesar de parecer feio, é bastante testado no RHEL4, RHEL5 e RHEL6.

Eu tive isso antes de o apoio do augeas estar em marionetes. Se eu estivesse escrevendo novamente hoje, examinaria a lente augeas iptables antes de recorrer exec { "perl ...": }.

Algumas definições globais para editar arquivos no local

Baseado em material originalmente de http://reductivelabs.com/trac/puppet/wiki/SimpleTextRecipes

# Ensure that the line "line" exists in "file":
# Usage: 
# append_if_no_such_line { dummy_modules:
#        file => "/etc/modules",
#        line => dummy 
# }
# 
define append_if_no_such_line($file, $line, $refreshonly = 'false') {
   exec { "/bin/echo '$line' >> '$file'":
      unless => "/bin/grep -Fxqe '$line' '$file'",
      refreshonly => $refreshonly,
   }
}

# Ensure that the line "line" exists in "file":
# Usage: 
# prepend_if_no_such_line { dummy_modules:
#        file => "/etc/modules",
#        line => dummy 
# }
# 
define prepend_if_no_such_line($file, $line, $refreshonly = 'false') {
   $line_no_slashes = slash_escape($line)
   exec { "/usr/bin/perl -p0i -e 's/^/$line_no_slashes\n/;' '$file'":
      unless => "/bin/grep -Fxqe '$line' '$file'",
      refreshonly => $refreshonly,
   }
}

define insert_line_after_if_no_such_line($file, $line, $after) {
    $line_no_slashes = slash_escape($line)
    $after_no_slashes = slash_escape($after)

    exec { "/usr/bin/perl -p0i -e 's/^($after_no_slashes)\$/\$1\n$line_no_slashes/m' '$file'":
        onlyif => "/usr/bin/perl -ne 'BEGIN { \$ret = 0; } \$ret = 1 if /^$line_no_slashes/; END { exit \$ret; }' '$file'",
    }
}

define insert_line_before_if_no_such_line($file, $line, $beforeline) {
    $line_no_slashes = slash_escape($line)
    $before_no_slashes = slash_escape($beforeline)

    exec { "/usr/bin/perl -p0i -e 's/^($before_no_slashes)\$/$line_no_slashes\n\$1/m' '$file'":
        onlyif => "/usr/bin/perl -ne 'BEGIN { \$ret = 0; } \$ret = 1 if /^$line_no_slashes/; END { exit \$ret; }' '$file'",
    }
}

Minha classe iptables:

class iptables {
   if $lsbmajdistrelease >= '6' {
     $primarychain = 'INPUT'
   } else {
     $primarychain = 'RH-Firewall-1-INPUT'
   }

   package {
      iptables: 
         ensure => installed   # "latest" would be too much
   }

   service { 
     iptables:
        enable    => true,    # default on
        ensure    => running, # start it up if it's stopped
        hasstatus => true,    # since there's no daemon
  }


   file {
     "/etc/sysconfig/iptables":
       ensure => present;
   }

   ##
   # Build up a config if it's missing components we expect; should
   # automatically repair a config if it's broken for really simple reasons
   ##

   # Very first thing: a comment at the top warning about our evil; add even if
   # we're not touching anything else...
   prepend_if_no_such_line { 
      "/etc/sysconfig/iptables comment":
         file => "/etc/sysconfig/iptables",
         line => "# This file partially managed by puppet; attempts to edit will result in magic reappearances"
   }

   # start
   # *filter
   insert_line_after_if_no_such_line {
      "/etc/sysconfig/iptables *filter":
         file    => "/etc/sysconfig/iptables",
         line    => "\\*filter",
         after   => "#.*",
         notify => Service[iptables],
   }

   # first default chain
   # :INPUT ACCEPT [0:0]
   insert_line_after_if_no_such_line {
      "/etc/sysconfig/iptables:INPUT":
         file   => "/etc/sysconfig/iptables",
         line   => ":INPUT ACCEPT \\[0:0\\]",
         after  => "\\*filter",
         notify => Service[iptables],
   }

   # second default chain
   # :FORWARD ACCEPT [0:0]
   insert_line_after_if_no_such_line {
      "/etc/sysconfig/iptables:FORWARD":
         file   => "/etc/sysconfig/iptables",
         line   => ":FORWARD ACCEPT \\[0:0\\]",
         after  => ":INPUT ACCEPT \\[\\d+:\\d+\\]",
         notify => Service[iptables],
   }


   # third default chain
   # :OUTPUT ACCEPT [0:0]
   insert_line_after_if_no_such_line {
      "/etc/sysconfig/iptables:OUTPUT":
         file   => "/etc/sysconfig/iptables",
         line   => ":OUTPUT ACCEPT \\[0:0\\]",
         after  => ":FORWARD ACCEPT \\[\\d+:\\d+\\]",
         notify => Service[iptables],
   }

   if $lsbmajdistrelease <= 5 {

      # Finally, the RH special chain
      # :RH-Firewall-1-INPUT - [0:0]
      insert_line_after_if_no_such_line {
         "/etc/sysconfig/iptables:RH-Firewall-1-INPUT":
            file   => "/etc/sysconfig/iptables",
            line   => ":RH-Firewall-1-INPUT - \\[0:0\\]",
            after  => ":OUTPUT ACCEPT \\[\\d+:\\d+\\]",
            notify => Service[iptables],
      }

      # redirect INPUT to RH chain
      # -A INPUT -j RH-Firewall-1-INPUT
      insert_line_after_if_no_such_line {
         "/etc/sysconfig/iptables:INPUT:RH-Firewall-1-INPUT":
            file   => "/etc/sysconfig/iptables",
            line   => "-A INPUT -j RH-Firewall-1-INPUT",
            after  => ":RH-Firewall-1-INPUT - \\[\\d+:\\d+\\]",
            notify => Service[iptables],
      }

      # redirect FORWARD to RH chain
      # -A FORWARD -j RH-Firewall-1-INPUT
      insert_line_after_if_no_such_line { 
         "/etc/sysconfig/iptables:FORWARD:RH-Firewall-1-INPUT":
            file   => "/etc/sysconfig/iptables",
            line   => "-A FORWARD -j RH-Firewall-1-INPUT",
            after  => "-A INPUT -j RH-Firewall-1-INPUT",
            notify => Service[iptables],
      }

   }

   # Let anything on localhost work...
   # -A $primarychain -i lo -j ACCEPT
   insert_line_after_if_no_such_line {
      "/etc/sysconfig/iptables:$primarychain lo":
         file    => "/etc/sysconfig/iptables",
         line   => "-A $primarychain -i lo -j ACCEPT",
         after  => "-A FORWARD -j $primarychain",
         notify => Service[iptables],
   }

   # And let through all the ICMP stuff:
   # -A $primarychain -p icmp --icmp-type any -j ACCEPT
   if $lsbmajdistrelease >= '6' {
     insert_line_after_if_no_such_line {
        "/etc/sysconfig/iptables:$primarychain icmp":
           file   => "/etc/sysconfig/iptables",
           line   => "-A $primarychain -p icmp -j ACCEPT",
           after  => "-A $primarychain -i lo -j ACCEPT",
           notify => Service[iptables],
     }
   } else {
     insert_line_after_if_no_such_line {
        "/etc/sysconfig/iptables:$primarychain icmp":
           file   => "/etc/sysconfig/iptables",
           line   => "-A $primarychain -p icmp --icmp-type any -j ACCEPT",
           after  => "-A $primarychain -i lo -j ACCEPT",
           notify => Service[iptables],
     }
   }

   # Finally, let anything that's part of an exisiting connection through:
   # -A $primarychain -m state --state ESTABLISHED,RELATED -j ACCEPT
   insert_line_after_if_no_such_line {
      "/etc/sysconfig/iptables:ESTABLISHED":
         file   => "/etc/sysconfig/iptables",
         line   => "-A $primarychain -m state --state ESTABLISHED,RELATED -j ACCEPT",
         after  => "-A $primarychain -p icmp --icmp-type any -j ACCEPT",
         notify => Service[iptables],
   }

   # Very last thing:
   # COMMIT
   append_if_no_such_line {
      "/etc/sysconfig/iptables:COMMIT":
         file   => "/etc/sysconfig/iptables",
         line   => "COMMIT",
         notify => Service[iptables],
   }

   # Next to last thing: reject!
   # -A $primarychain -j REJECT --reject-with icmp-host-prohibited
   insert_line_before_if_no_such_line {
      "/etc/sysconfig/iptables:final reject":
         file       => "/etc/sysconfig/iptables",
         line       => "-A $primarychain -j REJECT --reject-with icmp-host-prohibited",
         beforeline => "COMMIT",
         notify     => Service[iptables],
   }
}

# example:
# iptable_rule { "iptable:ssh":
#   rule => "-m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT"
# }
# change your mind about a rule, do this:
# iptable_rule { "iptable:ssh":
#   rule   => "-m state --state NEW -m tcp -p tcp --dport 22 -j ACCEPT",
#   ensure => "absent",
# }
define iptable_rule($rule, $ensure = 'present') {
   if $lsbmajdistrelease >= '6' {
     $primarychain = 'INPUT'
   } else {
     $primarychain = 'RH-Firewall-1-INPUT'
   }
   $iptablesline = "-A $primarychain $rule"
   case $ensure {
      default: { err ( "unknown ensure value $ensure" ) }
      present: {
         insert_line_before_if_no_such_line {
            "/etc/sysconfig/iptables:add $rule":
               file       => "/etc/sysconfig/iptables",
               line       => $iptablesline,
               beforeline => "-A $primarychain -j REJECT --reject-with icmp-host-prohibited",
               notify     => Service[iptables],
         }
      }
      absent: {
         delete_lines {
            "/etc/sysconfig/iptables:remove $rule":
               file    => "/etc/sysconfig/iptables",
               pattern => $iptablesline,
               notify  => Service[iptables],
         }
      }
   }
}

# Example:
# iptable_tcp_port { "iptable:ssh":
#    port => "22",
# }
# Example:
# iptable_tcp_port { "iptable:oracle:130.157.5.0/24":
#    port    => "1521",
#    source => "130.157.5.0/24",
# }
# (add ensure => "absent" to remove)
define iptable_tcp_port($port, $ensure = 'present', $source = 'ANY') {
   case $source {
      "ANY": {
         iptable_rule {
            "iptable_tcp_port:$port":
               rule   => "-m state --state NEW -m tcp -p tcp --dport $port -j ACCEPT",
               ensure => $ensure,
         }
      }
      default: {
         iptable_rule {
            "iptable_tcp_port:$port:$source":
               rule   => "-m state --state NEW -m tcp -p tcp --source $source --dport $port -j ACCEPT",
               ensure => $ensure,
         }
      }
   }
}

# Example:
# iptable_udp_port { "iptable:ntp":
#    port => "123",
# }
# (again, ensure => "absent" if needed)
define iptable_udp_port($port, $ensure = 'present', $source = 'ANY') {
   case $source {
      "ANY": {
         iptable_rule {
            "iptable_udp_port:$port":
               rule   => "-p udp -m udp --dport $port -j ACCEPT",
               ensure => $ensure,
         }
      }
      default: {
         iptable_rule {
            "iptable_udp_port:$port":
               rule   => "-p udp -m udp --source $source --dport $port -j ACCEPT",
               ensure => $ensure,
         }
      }
   }
}

Alguns exemplos de uso em outras classes:

class ssh {
  include iptables
  iptable_tcp_port {
    "iptables:ssh":
      port   => "22",
      ensure => "present"
   }
}
class ssh_restricted inherits ssh {
  Iptable_tcp_port["iptables:ssh"]{ensure => "absent"}
  iptable_tcp_port {
    "ssh:RESTRICTED":
      port   => "22",
      source => "X.Y.0.0/16", 
      ensure => "present";
   }
}

class apache {
  iptable_tcp_port {
    "iptables:http":
      require => Service["httpd"],
      port => "80";
  }
}

class apache::secure {
  iptable_tcp_port {
    "iptables:https":
      require => Service["httpd"],
      port => "443";
  }
}

class snmp {
  iptable_udp_port { "iptables:snmp": port => "161" }
}
freiheit
fonte
6

O Puppet Labs tem um exemplo no wiki: Padrões de Iptables do Módulo

Resumindo: você cria fragmentos para cada serviço e os instala invocando o tipo definido ipt_fragment:

ipt_fragment {"filter-ftp": assegure => presente}

Quando um fragmento é instalado (eles estão em /etc/iptables.d/), ele dispara a execução de um script que concatena todos os fragmentos e reinicia o iptables.

jbbuckley
fonte
4

A questão é: o que você pretende realizar?

Colocar o iptables no Puppet é fácil: coloque o script no servidor de fantoches e sirva-o onde for necessário. Se precisar de alguma personalização, torne-o um modelo.

Agora, talvez você queira algo como "Se o host X tiver um servidor da Web, as portas 80 e 443 devem estar abertas". Nesse caso, o que eu sugiro que você faça é compor o script a partir de várias partes do arquivo, usando o módulo Comumconcatenated_file ou concatfilepart(eu prefiro o último, que permite o pedido, mas não está disponível em todos os garfos - aponte para você do camptocamp, que possui).

Essas partes do arquivo podem ser facilmente gravadas usando modelos. O truque é que você exporte o concatfilepartna Apacheclasse (possivelmente chamando um define que prepare um com concatfilepartbase em parâmetros como endereço IP e porta) e na iptablesclasse você perceberia todos os concatfilepartitens exportados marcados iptablesou algo parecido.

Se você fizer isso, eu adoraria ver esse módulo no github. Eu nunca consegui escrever um módulo iptables. :-)

Daniel C. Sobral
fonte