使用 iptables 管理防火墙
Programming can be fun, so can cryptography; however they should not be combined.
— Kreitzberg and Shneiderman
C 编程语言被形容为 “只写” 的语言;它是如此的简洁、高效, 甚至你自己读自己写过的代码都可能很难理解。 同样地,Linux 内核内置的包过滤防火墙的 iptables 的配置也是如此。 一条原始的 iptables 命令规则看上去像这样:
iptables -A INPUT -d 10.0.2.15/32 -p tcp -m tcp --dport 80 -j ACCEPT
除非你会因为掌握了命令行的这些似乎毫无意义的字符串而获得男子气概 (诚然这是 UNIX 系统管理员的职业病), 否则,能够以更加象征性和可读性的方式来表达防火墙规则是更好的选择。 Puppet 在这方面可以为我们提供帮助,因为我们可以用它对 iptables 的实现细节进行抽象, 并通过参考管理员所控制的服务角色来定义防火墙规则,例如:
iptables::role { "web-server": }
准备工作
你需要我们在第 5 章 为配置文件添加配置行 一节中创建的 append_if_no_such_line 函数。
操作步骤
创建一个 iptables 模块:
# mkdir /etc/puppet/modules/iptables # mkdir /etc/puppet/modules/iptables/manifests # mkdir /etc/puppet/modules/iptables/files
使用如下内容创建 /etc/puppet/modules/iptables/manifests/init.pp 文件:
class iptables { file { [ "/root/iptables", "/root/iptables/hosts", "/root/iptables/roles" ]: ensure => directory, } file { "/root/iptables/roles/common": source => "puppet:///modules/iptables/common.role", notify => Exec["run-iptables"], } file { "/root/iptables/names": source => "puppet:///modules/iptables/names", notify => Exec["run-iptables"], } file { "/root/iptables/iptables.sh": source => "puppet:///modules/iptables/iptables.sh", mode => "755", notify => Exec["run-iptables"], } file { "/root/iptables/hosts/${hostname}": content => "export MAIN_IP=${ipaddress}\n", replace => false, require => File["/root/iptables/hosts"], notify => Exec["run-iptables"], } exec { "run-iptables": cwd => "/root/iptables", command => "/usr/bin/test -f hosts/${hostname} && /root/iptables/iptables.sh && /sbin/iptables-save > /etc/iptables.rules", refreshonly => true, } append_if_no_such_line { "restore iptables rules": file => "/etc/network/interfaces", line => "pre-up iptables-restore < /etc/iptables.rules", } define role() { include iptables file { "/root/iptables/roles/${name}": source => "puppet:///modules/iptables/${name}.role", replace => false, require => File["/root/iptables/roles"], notify => Exec["run-iptables"], } append_if_no_such_line { "${name} role": file => "/root/iptables/hosts/${hostname}", line => ". `dirname \$0`/roles/${name}", require => File["/root/iptables/hosts/${hostname}"], notify => Exec["run-iptables"], } } }
使用如下内容创建 /etc/puppet/modules/iptables/files/iptables.sh 文件:
# Server names and ports . `dirname $0`/names # Interfaces (override in host-specific file if necessary) export EXT_INTERFACE=eth0 # Flush and remove all chains iptables -P INPUT ACCEPT iptables -P OUTPUT ACCEPT iptables -F iptables -X # Allow all traffic on loopback interface iptables -I INPUT 1 -i lo -j ACCEPT iptables -I OUTPUT 1 -o lo -j ACCEPT # Allow established and related connections iptables -I INPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT iptables -I OUTPUT 2 -m state --state ESTABLISHED,RELATED -j ACCEPT # Include machine specific settings HOST_RULES=`dirname $0`/hosts/`hostname -s` [ -f ${HOST_RULES} ] && . ${HOST_RULES} [ "${MAIN_IP}" == "" ] && ( echo No MAIN_IP was set, \ please set the primary IP address in ${HOST_RULES}. ; exit 1 ) # Include common settings . `dirname $0`/roles/common # Drop all non-matching packets iptables -A INPUT -j LOG --log-prefix "INPUT: " iptables -A INPUT -j DROP iptables -A OUTPUT -j LOG --log-prefix "OUTPUT: " iptables -A OUTPUT -j DROP echo -e "Test remote login and then:\n iptables-save \ >/etc/iptables.rules"
使用如下内容创建 /etc/puppet/modules/iptables/files/names 文件:
# Servers export PUPPETMASTER=10.0.2.15 # Well-known ports export DNS=53 export FTP=21 export GIT=9418 export HEARTBEAT=694 export IMAPS=993 export IRC=6667 export MONIT=2828 export MYSQL=3306 export MYSQL_MASTER=3307 export NRPE=5666 export NTP=123 export POSTGRES=5432 export PUPPET=8140 export RSYNCD=873 export SMTP=25 export SPHINX=3312 export SSH=22 export STARLING=3307 export SYSLOG=514 export WEB=80 export WEB_SSL=443 export ZABBIX=10051
使用如下内容创建 /etc/puppet/modules/iptables/files/common.role 文件:
# Common rules for all hosts iptables -A INPUT -p tcp -m tcp -d ${MAIN_IP} --dport ${SSH} -j ACCEPT iptables -A INPUT -p ICMP --icmp-type echo-request -j ACCEPT iptables -A OUTPUT -p ICMP --icmp-type echo-request -j ACCEPT iptables -A OUTPUT -p tcp --dport ${SSH} -j ACCEPT iptables -A OUTPUT -p tcp --dport ${SMTP} -j ACCEPT iptables -A OUTPUT -p udp --dport ${NTP} -j ACCEPT iptables -A OUTPUT -p tcp --dport ${NTP} -j ACCEPT iptables -A OUTPUT -p udp --dport ${DNS} -j ACCEPT iptables -A OUTPUT -p tcp --dport ${WEB} -j ACCEPT iptables -A OUTPUT -p tcp --dport ${WEB_SSL} -j ACCEPT iptables -A OUTPUT -p tcp -d ${PUPPETMASTER} --dport ${PUPPET} -j ACCEPT iptables -A OUTPUT -p tcp --dport ${MYSQL} -j ACCEPT # Drop some commonly probed ports iptables -A INPUT -p tcp --dport 23 -j DROP # telnet iptables -A INPUT -p tcp --dport 135 -j DROP # epmap iptables -A INPUT -p tcp --dport 139 -j DROP # netbios iptables -A INPUT -p tcp --dport 445 -j DROP # Microsoft DS iptables -A INPUT -p udp --dport 1433 -j DROP # SQL server iptables -A INPUT -p tcp --dport 1433 -j DROP # SQL server iptables -A INPUT -p udp --dport 1434 -j DROP # SQL server iptables -A INPUT -p tcp --dport 1434 -j DROP # SQL server iptables -A INPUT -p tcp --dport 2967 -j DROP # SSC-agent
使用如下内容创建 /etc/puppet/modules/iptables/files/web-server.role 文件:
# Access to web iptables -A INPUT -p tcp -d ${MAIN_IP} --dport ${WEB} -j ACCEPT # Send mail from web applications iptables -A OUTPUT -p tcp --dport ${SMTP} -j ACCEPT
使用如下内容创建 /etc/puppet/modules/iptables/files/puppet-server.role 文件:
# Access to puppet iptables -A INPUT -p tcp -d ${MAIN_IP} --dport ${PUPPET} -j ACCEPT
在你的 Puppetmaster 节点上包含如下内容:
iptables::role { "web-server": } iptables::role { "puppet-server": }
运行 Puppet:
# puppet agent --test info: Retrieving plugin info: Caching catalog for cookbook.bitfieldconsulting.com info: Applying configuration version '1311682880' notice: /Stage[main]/Iptables/File[/root/iptables]/ensure: created notice: /Stage[main]/Iptables/File[/root/iptables/names]/ensure: defined content as '{md5}9bb004a7d2c6d70616b149d044c22669' info: /Stage[main]/Iptables/File[/root/iptables/names]: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]/Iptables/File[/root/iptables/hosts]/ensure: created notice: /Stage[main]/Iptables/File[/root/iptables/hosts/cookbook]/ ensure: defined content as '{md5}d00bc730514bbb74cdef3dad70058a81' info: /Stage[main]/Iptables/File[/root/iptables/hosts/cookbook]: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]//Node[cookbook]/Iptables::Role[web-server]/ Append_if_no_such_line[web-server role]/Exec[/bin/echo '. `dirname $0`/roles/web-server' >> '/root/iptables/hosts/cookbook']/returns: executed successfully info: /Stage[main]//Node[cookbook]/Iptables::Role[web-server]/ Append_if_no_such_line[web-server role]/Exec[/bin/echo '. `dirname $0`/roles/web-server' >> '/root/iptables/hosts/cookbook']: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]//Node[cookbook]/Iptables::Role[puppetserver]/ Append_if_no_such_line[puppet-server role]/Exec[/bin/echo '. `dirname $0`/roles/puppet-server' >> '/root/iptables/hosts/ cookbook']/returns: executed successfully info: /Stage[main]//Node[cookbook]/Iptables::Role[puppet-server]/ Append_if_no_such_line[puppet-server role]/Exec[/bin/echo '. `dirname $0`/roles/puppet-server' >> '/root/iptables/hosts/ cookbook']: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]/Iptables/File[/root/iptables/roles]/ensure: created notice: /Stage[main]//Node[cookbook]/Iptables::Role[puppetserver]/ File[/root/iptables/roles/puppet-server]/ensure: defined content as '{md5}c30a13f7792525c181e14e78c9a510cd' info: /Stage[main]//Node[cookbook]/Iptables::Role[puppet-server]/ File[/root/iptables/roles/puppet-server]: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]//Node[cookbook]/Iptables::Role[web-server]/ File[/root/iptables/roles/web-server]/ensure: defined content as '{md5}11e5747cb2737903ffc34133f5fe2452' info: /Stage[main]//Node[cookbook]/Iptables::Role[web-server]/ File[/root/iptables/roles/web-server]: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]/Iptables/File[/root/iptables/roles/common]/ ensure: defined content as '{md5}116f57d4e31f3e0b351da6679dca15e3' info: /Stage[main]/Iptables/File[/root/iptables/roles/common]: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]/Iptables/File[/root/iptables/iptables.sh]/ ensure: defined content as '{md5}340ff9fb5945e9fc7dd78b21f45dd823' info: /Stage[main]/Iptables/File[/root/iptables/iptables.sh]: Scheduling refresh of Exec[run-iptables] notice: /Stage[main]/Iptables/Exec[run-iptables]: Triggered 'refresh' from 8 events notice: /Stage[main]/Iptables/Append_if_no_such_line[restore iptables rules]/Exec[/bin/echo 'pre-up iptables-restore < /etc/ iptables.rules' >> '/etc/network/interfaces']/returns: executed successfully notice: Finished catalog run in 4.86 seconds
检查要求的规则是否已被安装:
# iptables -nL Chain INPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT tcp -- 0.0.0.0/0 10.0.2.15 tcp dpt:80 ACCEPT tcp -- 0.0.0.0/0 10.0.2.15 tcp dpt:8140 ACCEPT tcp -- 0.0.0.0/0 10.0.2.15 tcp dpt:22 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmp type 8 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:23 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:135 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:139 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:445 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:1433 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1433 DROP udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:1434 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:1434 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:2967 LOG all -- 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 4 prefix `INPUT: ' DROP all -- 0.0.0.0/0 0.0.0.0/0 Chain FORWARD (policy ACCEPT) target prot opt source destination Chain OUTPUT (policy ACCEPT) target prot opt source destination ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 ACCEPT all -- 0.0.0.0/0 0.0.0.0/0 state RELATED,ESTABLISHED ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:25 ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmp type 8 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:22 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:25 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:123 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:123 ACCEPT udp -- 0.0.0.0/0 0.0.0.0/0 udp dpt:53 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:80 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:443 ACCEPT tcp -- 0.0.0.0/0 10.0.2.15 tcp dpt:8140 ACCEPT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:3306 LOG all -- 0.0.0.0/0 0.0.0.0/0 LOG flags 0 level 4 prefix `OUTPUT: ' DROP all -- 0.0.0.0/0 0.0.0.0/0
工作原理
为了创建一套合适的防火墙规则,我们需要知道节点的主 IP 地址以及其运行了哪些服务。 我们还需要添加一些所有的机器都要设置的共同规则(例如,允许 SSH),并运行一系列的 iptables 命令以激活我们已经生成的规则。 之后我们还要保存这些规则,以便使这些规则可以在开机时恢复执行。 下面介绍所有的这一切是如何完成的。
首先,我们创建一个 names 文件为常用的端口定义 shell 变量。 这意味着,当我们定义防火墙规则时,可以引用变量,例如对于 MySQL 服务可以使用变量 ${MYSQL} 取代数值端口号 3306。
common.role 文件包含了一些对所有机器都有用的规则。编辑这个文件以适应你自己的所有机器 (例如,你可能仅允许从指定的 IP 范围访问 SSH)。
web-server.role 和 puppet-server.role 文件包含了两个特定角色的规则。 你可以添加更多的文件用于定义你的网络中所需的众多角色:例如,数据库服务器、应用服务器、 DNS 服务器,等等。文件中所有的规则都具有如下的格式:
iptables -A INPUT -p tcp -d ${MAIN_IP} --dport ${WEB} -j ACCEPT
通常,你只需要修改 ${WEB} 这部分:将其替换为另一个端口名(定义在 names 文件中) 的变量引用(例如 ${POSTGRES})。如果你需要更多的端口名,在 names 文件中添加相应的定义。
iptables.sh 脚本读取其他的所有文件并执行规则要求的 iptables 命令。 每当相关文件有任何改变,Puppet 就执行这个脚本,因此要想刷新防火墙, 你需要做的工作就是改变相关的配置并运行 Puppet。
Puppet 还会将当前的规则集保存在 /etc/iptables.rules 文件中。 为了让机器重启后加载规则集文件,Puppet 在 /etc/network/interfaces 文件中添加了如下一行:
pre-up iptables-restore < /etc/iptables.rules
所有这一切意味着,在相关的模块中(例如 apache 模块), 你只要简单地包含如下一行即可创建相应的防火墙:
iptables::role { "web-server": }
一旦防火墙被激活,任何不符合规则的数据包将被阻止并记录到 /var/log/messages 日志文件。 检查这个文件,以帮助解决防火墙的任何问题。
更多用法
如果在你的规则中引用了某些特定的机器(例如,你的监控服务器), 可以在 names 文件中添加如下的定义:
MONITOR=10.0.2.15
然后在适当的位置(例如 common.role 文件中),你可以允许来自这台机器的访问, 例如,允许来自监控服务器对指定主机 NRPE 端口的访问规则如下:
iptables -A INPUT -p tcp -m tcp -d ${MAIN_IP} -s ${MONITOR} --dport ${NRPE} -j ACCEPT
你也可以用这种方法指定数据库服务器,以及在 .role 文件中需要引用特定的 IP 地址、 网络地址和地址范围等情况。
像这样动态生成防火墙规则集对于云基础设施是非常有用的, 云中的服务器列表会因为节点的创建和销毁而不断地变化。 对于需要触发防火墙重建的任何资源,你只要在此资源中添加如下代码即可:
notify => Exec["run-iptables"],
你可能有一个由版本控制系统维护的或通过 cloud API (例如,Rackspace 或 Amazon EC2) 自动更新的 “主服务器列表(master server list)”。 可以在 Puppet 中将这个列表定义成一个 file 资源,通过在此资源中使用 notify 参数即可触发防火墙的重建,所以每次当你检入(check in)主服务器列表的变化, 每台机器上运行的 Puppet 将相应地更新其防火墙。
当然,这种高度的自动化意味着你需要对你检入的内容格外小心, 因为任何错误都可能会导致整个基础设施离线。
测试变更的一种好方法是对用于测试的 Puppet 配置清单使用一个单独的 Git 分支, 在分支中仅将变更应用到一到两台服务器。一旦你验证了变更的正确性, 就可以将其合并到主分支并回滚到主分支。