Hack 和 / - 使用 Nmap 的动态配置文件

作者:Kyle Rankin

工具的妙处在于,您常常可以将其误用于完全不同的目的。螺丝刀的末端可以充当临时的锤子;黄油刀可以当螺丝刀,甚至在紧急情况下,回形针也可以代替钥匙。通常,您可能认为 nmap 是一种安全工具。毕竟,当您想要测试机器是否存在开放的、易受攻击的端口时,它是理想的选择。但前几天,我意识到 nmap 还有另一种用途——一种扫描我的网络并基于哪些机器响应我的扫描来构建动态配置文件的方法。

Munin 很流行

整个项目开始于我决定在我的服务器上部署 Munin,以便我可以为我网络上的每台机器绘制趋势数据图。Munin 是一个很棒的趋势分析工具,因为一旦您安装了代理,它通常会自动发现要监控和绘制图表的哪些服务和统计数据。但对我来说,缺点是我已经有一个装满服务器的网络。我必须在每台机器上安装代理已经够糟糕了,但我还必须在我的 Munin 服务器上手动构建一个巨大的配置文件,其中列出了它应该监控的每台服务器。此外,每当我向网络添加一台机器时,我的构建过程中又多了一个步骤,因为我必须将该新服务器添加到我的 Munin 配置中。

我是自动化的忠实粉丝,我想一定有更简单的方法可以将我所有的机器添加到这个文件中。当您查看 Munin 配置文件时,它似乎非常适合自动化。

dbdir   /var/lib/munin
htmldir /var/www/munin
logdir  /var/log/munin
rundir  /var/run/munin
tmpldir /etc/munin/templates

[web1.example.net]
        address web1.example.net

[web2.example.net]
        address web2.example.net

[db1.example.net]
        address db1.example.net

[db2.example.net]
        address db2.example.net

通用 munin.conf 文件的语法非常简单明了。首先,定义一些目录,然后在方括号对内定义每个服务器。在这些方括号内,您可以为服务器分配一个名称,或者只使用主机名。之后,下一行列出该服务器的主机名或 IP 地址。在上面的示例中,我定义了四个服务器。

如果我想自动生成这个配置文件,我必须想办法检测我的网络上哪些服务器正在运行 Munin。Munin 使这变得简单,因为默认情况下,每台服务器都有一个 Munin 代理在端口 4949 上监听。我所要做的就是使用 nmap 扫描网络,并列出所有端口 4949 处于打开状态的机器。我想我可以解析该输出并将其附加到我的 munin.conf 文件中,然后也许可以创建一个 vim 宏来逐行浏览并格式化它。

使用可 Grep 输出的 Nmap

第一步是找到正确的 nmap 语法,以便它可以扫描我的网络并列出所有正在监听端口 4949 的机器。首先,我尝试了标准命令:

$ nmap -p 4949 10.1.1.0/24

Starting Nmap 4.11 ( http://www.insecure.org/nmap/ ) 
 ↪at 2010-03-01 20:18 PST

Interesting ports on 10.1.1.1:
PORT     STATE  SERVICE
4949/tcp closed unknown
MAC Address: 00:00:0C:01:CD:05 (Cisco Systems)

Interesting ports on purple1.example.net (10.1.1.50):
PORT     STATE  SERVICE
4949/tcp closed unknown
MAC Address: 08:00:20:CF:9D:D7 (SUN Microsystems)

Interesting ports on web1.example.net (10.1.1.53):
PORT     STATE SERVICE
4949/tcp open  unknown
MAC Address: 00:50:56:92:34:02 (VMWare)

Interesting ports on web2.example.net (10.1.1.67):
PORT     STATE SERVICE
4949/tcp open  unknown
MAC Address: 00:30:48:A0:12:98 (Supermicro Computer)
. . .

正如您所看到的,对于 nmap 找到的每台机器,它都会列出 IP、端口是否打开,甚至尝试识别机器的类型。即使您可以从这个输出中 grep 出端口打开的机器,但使用多行输出解析所有内容也会非常痛苦。相反,我使用了 nmap 的 -oG 参数,它告诉 nmap 以“可 grep 格式”输出,以及 - 参数,它告诉 nmap 将该输出发送到 STDOUT。结果更容易解析。

$ nmap -oG - -p 4949 10.1.1.0/24
# Nmap 4.11 scan initiated Mon Mar  1 20:26:45 2010 as: 
 ↪nmap -oG - -p 4949
# 10.1.1.0/24 
Host: 10.1.1.1 ()      Ports: 4949/closed/tcp/////
Host: 10.1.1.50 (purple1.example.net)    Ports: 4949/closed/tcp/////
Host: 10.1.1.53 (web1.example.net)       Ports: 4949/open/tcp/////
Host: 10.1.1.67 (web2.example.net)       Ports: 4949/open/tcp/////
. . .

现在我只需 grep “open”,就可以获得运行 Munin 的所有机器的列表。

$ nmap -oG - -p 4949 10.1.1.0/24 | grep open
Host: 10.1.1.53 (web1.example.net)     Ports: 4949/open/tcp/////
Host: 10.1.1.67 (web2.example.net)     Ports: 4949/open/tcp/////
Perl 来救援

一旦我开始研究正则表达式来解析这个输出并生成我需要的语法,我就意识到我应该放弃 vim,而只是编写一个脚本,为我构建整个配置文件,并使用 cron 运行该脚本。这样,我再也不必添加新服务器了。唯一的挑战是,我想扫描多个子网,并且我发现有时 nmap 没有为我将 IP 地址解析为主机名。清单 1 显示了结果脚本。

清单 1. 构建配置文件的脚本

#!/usr/bin/perl

my $munin_dir = '/etc/munin';
my $munin_config = 'munin.conf';
my $munin_config_temp = 'munin.conf.tmp';
my $node_port = '4949';
my $nmap = "nmap -oG - -p ";
my %subnets = ( 
                "10.1.1.0/24" => "VLAN1",
                "10.1.5.0/24" => "VLAN5",
                "10.1.6.0/24" => "VLAN6",
             );

# iterate through each subnet and perform the nmap scan
foreach $subnet (keys %subnets){
   open NMAP, "$nmap $node_port $subnet | grep open |" 
   ↪or die "Can't run nmap: $!\n";
   while (<NMAP>){
      $ip = $host = "";
# parse out the hostname and IP address
      /Host: (\d+\.\d+\.\d+\.\d+) \((.*?)\)/;
      $ip = $1; $host = $2;

      next if($ip eq "");

# sometimes nmap doesn't do rDNS properly, 
# get it manually in that case
      if($host eq ""){
         $host = `dig -x $ip +short` or $host = $ip;
         chomp $host;
         $host =~ s/\.$//;
      }

      $munin_hosts{$host} = $ip;
   }
   close NMAP;
}

# output to a temp file in case munin 
# runs while this script is open
open OUTFILE, "> $munin_dir/$munin_config_temp" or die "Can't open
$munin_dir/$munin_config_temp: $!\n";

# first print out the standard header for the munin file
print OUTFILE <<END_HEAD;
dbdir   /var/lib/munin
htmldir /var/www/munin
logdir  /var/log/munin
rundir  /var/run/munin
tmpldir /etc/munin/templates

END_HEAD

# then print out the config for each host
foreach $host (sort keys %munin_hosts){
   print OUTFILE "\[$host\]\n\taddress $host\n";
   # add any extra munin options for each host here
   print OUTFILE "\n";
}
close OUTFILE;

system("mv $munin_dir/$munin_config_temp $munin_dir/$munin_config");

除了哈希和一些正则表达式的乐趣之外,这个脚本的大部分都是基本的 Perl。一旦我手动测试了几次并对其感到满意,我就继续将脚本复制到 /etc/cron.daily 中。当然,在我的真实网络上,我添加了一些其他花哨的功能。例如,我网络上的每台服务器都有一个 DNS TXT 记录,说明该服务器的作用。这是一个在很多方面都有用的实践,但在这种情况下,我发现因为我对类似的服务器使用了相同的 TXT 记录,所以我可以查找它并使用它将服务器分组在该标题下。

虽然这个脚本对于 Munin 配置非常有效,但您也可以使用相同的程序来扫描任意数量的服务并构建配置。我可以想到可以为 Nagios 生成配置文件的脚本,轮询 SNMP 的程序或任何其他通过已知端口监控多个服务器的程序。

Kyle Rankin 是旧金山湾区的系统架构师,并且是许多书籍的作者,包括Ubuntu Server 官方指南、《Knoppix Hacks》和《Ubuntu Hacks》。他目前是 North Bay Linux 用户组的主席。

加载 Disqus 评论