puppet-firewall模块

iptables是一个配置Linux内核防火墙的命令行工具,通过设定一些特殊的规则,以允许或拒绝数据包通过。

puppet-firewall模块是由Puppet公司维护的官方模块, 用于管理防火墙和其规则。该模块通过扩展自定义资源类型来管理firewall规则和 iptables chains, 当前支持iptables和ip6tables。

puppet-firewall项目地址:'https://github.com/puppetlabs/puppetlabs-firewall'

1.先睹为快

不想看下面大段的代码解析,已经跃跃欲试了?

先别激动,这个模块的使用是有风险的,操作不慎会把自己也拒之门外,请确保可以通过除ssh外的方式登陆。

创建learn_firewall.pp文件并编辑:

  class my_fw::pre {
    Firewall {
      require => undef,
    }

    # Default firewall rules
    firewall { '000 accept all icmp':
      proto  => 'icmp',
      action => 'accept',
    }->
    firewall { '001 accept all to lo interface':
      proto   => 'all',
      iniface => 'lo',
      action  => 'accept',
    }->
    firewall { '002 reject local traffic not on loopback interface':
      iniface     => '! lo',
      proto       => 'all',
      destination => '127.0.0.1/8',
      action      => 'reject',
    }->
    firewall { '003 accept related established rules':
      proto  => 'all',
      state  => ['RELATED', 'ESTABLISHED'],
      action => 'accept',
    }
  }

  class my_fw::post {
      firewall { '999 drop all':
        proto  => 'all',
        action => 'drop',
        before => undef,
      }
  }

  class my_fw {
    firewall { '004 Allow inbound SSH':
      dport    => 22,
      proto    => tcp,
      action   => accept,
      provider => 'iptables',
    }
    firewall { '005 Allow inbound HTTP':
      dport    => 80,
      proto    => tcp,
      action   => accept,
      provider => 'iptables',
    }
  }

  Firewall {
    before  => Class['my_fw::post'],
    require => Class['my_fw::pre'],
  }

  class { ['my_fw::pre', 'my_fw::post','my_fw']: }

  class { 'firewall': }

在终端下输入以下命令:

$ puppet apply -v learn_firewall.pp

在执行该命令前,操作系统的防火墙规则为空:

$ iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

在执行完成该命令后,防火墙规则发生了以下变化:

$ iptables -L

Chain INPUT (policy ACCEPT)
target     prot opt source               destination
ACCEPT     icmp --  anywhere             anywhere             /* 000 accept all icmp */
ACCEPT     all  --  anywhere             anywhere             /* 001 accept all to lo interface */
REJECT     all  --  anywhere             loopback/8           /* 002 reject local traffic not on loopback interface */ reject-with icmp-port-unreachable
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED /* 003 accept related established rules */
ACCEPT     tcp  --  anywhere             anywhere             multiport dports ssh /* 004 Allow inbound SSH */
ACCEPT     tcp  --  anywhere             anywhere             multiport dports http /* 005 Allow inbound HTTP */
ACCEPT     all  --  anywhere             anywhere             state RELATED,ESTABLISHED
ACCEPT     icmp --  anywhere             anywhere
ACCEPT     all  --  anywhere             anywhere
ACCEPT     tcp  --  anywhere             anywhere             state NEW tcp dpt:ssh
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited
DROP       all  --  anywhere             anywhere             /* 999 drop all */

Chain FORWARD (policy ACCEPT)
target     prot opt source               destination
REJECT     all  --  anywhere             anywhere             reject-with icmp-host-prohibited

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination

2.代码讲解

2.1 class firewall

firewall类用于管理Iptables软件包和服务,会根据内核类型申明不同的类进行管理。

class firewall (
  $ensure       = running,
  $pkg_ensure   = present,
  $service_name = $::firewall::params::service_name,
  $package_name = $::firewall::params::package_name,
) inherits ::firewall::params {
  case $ensure {
    /^(running|stopped)$/: {
      # Do nothing.
    }
    default: {
      fail("${title}: Ensure value '${ensure}' is not supported")
    }
  }

  case $::kernel {
    'Linux': {
      class { "${title}::linux":
        ensure       => $ensure,
        pkg_ensure   => $pkg_ensure,
        service_name => $service_name,
        package_name => $package_name,
      }
    }
    'FreeBSD': {
    }
    default: {
      fail("${title}: Kernel '${::kernel}' is not currently supported")
    }
  }
}

以Linux为例,firewall::linux会根据操作系统的不同调用对应的firewall::linux::xxx类:

  case $::operatingsystem {
    'RedHat', 'CentOS', 'Fedora', 'Scientific', 'SL', 'SLC', 'Ascendos',
    'CloudLinux', 'PSBM', 'OracleLinux', 'OVS', 'OEL', 'Amazon', 'XenServer': {
      class { "${title}::redhat":
        ensure       => $ensure,
        enable       => $enable,
        package_name => $package_name,
        service_name => $service_name,
        require      => Package['iptables'],
      }
    }
    'Debian', 'Ubuntu': {
      class { "${title}::debian":
        ensure       => $ensure,
        enable       => $enable,
        package_name => $package_name,
        service_name => $service_name,
        require      => Package['iptables'],
      }
    }
    ...
    }

以RedHat为例,firewall::linux::redhat会根据操作系统和版本的不同跳转到相应的逻辑。通过这个模块可以发现,firewall类仅完成了安装软件包 和管理服务状态,但要维护一个支持多平台和版本的模块并非易事,需要投入大量的精力进去,这也是社区模式可以得到众多公司认可的原因。

  # RHEL 7 and later and Fedora 15 and later require the iptables-services
  # package, which provides the /usr/libexec/iptables/iptables.init used by
  # lib/puppet/util/firewall.rb.
  if ($::operatingsystem != 'Amazon')
  and (($::operatingsystem != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0)
  or  ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0)) {
    service { 'firewalld':
      ensure => stopped,
      enable => false,
      before => Package[$package_name],
    }
  }

  if $package_name {
    package { $package_name:
      ensure => $package_ensure,
      before => Service[$service_name],
    }
  }

  if ($::operatingsystem != 'Amazon')
  and (($::operatingsystem != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0)
  or  ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0)) {
  ...
  # Redhat 7 selinux user context for /etc/sysconfig/iptables is set to unconfined_u
  case $::selinux {
    #lint:ignore:quoted_booleans
    'true',true: {
      case $::operatingsystemrelease {
        /^(6|7)\..*/: { $seluser = 'unconfined_u' }
        default: { $seluser = 'system_u' }
      }
    }
    #lint:endignore
    default:     { $seluser = undef }
  }

  file { "/etc/sysconfig/${service_name}":
    ensure  => present,
    owner   => 'root',
    group   => 'root',
    mode    => '0600',
    seluser => $seluser,
  }
}

在上述代码中,需要理解以下新知识点:

第一点,versioncmp函数用于比较两个版本号并返回比较结果,例如:

$result = versioncmp(a, b)

  • a大于b,返回1

  • a等于b,返回0

  • a小于b,返回-1

第二点,要理解运算的优先级顺序,在上述代码出现了一段比较复杂的条件语句:

if ($::operatingsystem != 'Amazon')
  and (($::operatingsystem != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0)
  or  ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0))

首先()的优先级最高,因此以下表达式会优先进行计算:

  • ($::operatingsystem != 'Amazon')

  • (($::operatingsystem != 'Fedora' and versioncmp($::operatingsystemrelease, '7.0') >= 0))

  • ($::operatingsystem == 'Fedora' and versioncmp($::operatingsystemrelease, '15') >= 0))

其次,==的优先级等于!=高于>=高于and。 最后是最外层的and/or运算if statement1 and statement2 or statement 3,那么其运算顺序是哪一种?

  • if (statement1 and statement2) or (statement 3)

  • if statement1 and (statement2 or statement 3)

答案是前一种,因为and的优先级高于or

第三点,掌握case条件语句的语法。

case条件语句和if条件语句类似,均是选择其中的一个Puppet代码块进行执行,但其更适合用于字符串和数值的匹配。

case $facts['name'] {
  'A':       { include role::case1 } 
  'B', 'C':  { include role::case2  } 
  /^(D|E)$/: { include role::case3  } 
  default:   { include role::default_case }
}

2.2 type firewall

资源类型firewall用于管理防火墙规则,以下举例说明如何在真实环境中使用该类型:

2.2.1 为apache开启80和443端口

firewall { '100 allow http and https access':
    dport  => [80, 443],
    proto  => tcp,
    action => accept,
  }

2.2.2 丢弃FIN/RST/ACK包如果没有对应的SYN包

firewall { '002 drop NEW external website packets with FIN/RST/ACK set and SYN unset':
  chain     => 'INPUT',
  state     => 'NEW',
  action    => 'drop',
  proto     => 'tcp',
  sport     => ['! http', '! 443'],
  source    => '! 10.0.0.0/8',
  tcp_flags => '! FIN,SYN,RST,ACK SYN',
}

2.2.3 SNAT 10.1.2.0/24

firewall { '100 snat for network foo2':
  chain    => 'POSTROUTING',
  jump     => 'MASQUERADE',
  proto    => 'all',
  outiface => 'eth0',
  source   => '10.1.2.0/24',
  table    => 'nat',
}

2.3 type firewallchain

资源类型firewallchain用于管理管理防火墙的规则链,以下举例说明如何在真实环境中使用该类型:

2.3.1 默认丢弃INPUT链上的包

  firewallchain { 'INPUT:filter:IPv4':
    ensure => present,
    policy => drop,
    before => undef,
  }

需要说明的是,这个模块有一定的 局限性,如只支持管理iptable和ip6tables。此外,在和Neutron同时使用时会遇到iptable规则的冲突问题。

3.扩展阅读

4.动手练习

  1. 本章给出的第一个示例learn_firewall.pp存在一些问题,当修改防火墙规则时,旧的规则不会被删除,请修复这个问题

  2. 为OpenStack Nova服务编写firewall规则,开放相应的服务端口

Last updated