借助Squid代理服务器,建立灵活的访问控制系统

并非只有大企业和核实验室才需要互联网访问策略和执行该策略的手段。我家制定有互联网访问策略,而我用来执行该策略的方法适用于几乎任何企业组织。拿我家来说,我倒不过于担心外部安全威胁。我们的网络在NAT路由器后面,无线网络使用了相当复杂的密码。我们的工作站不是Linux电脑,就是打上相应补丁的Windows电脑。不,我们担心的问题恰恰来自网络内部:孩子们喜欢玩网络游戏,而这常常妨碍其他事务和家庭作业。

我们还担心,孩子们无意中浏览到不该访问的网络内容。当然,我们不是在保护什么核机密或知识产权,而是让全家人都能够顺利使用网络,又没有不必要的担忧。

我和妻子通常不在乎孩子们玩在线游戏还是播放流媒体。但是如果家庭作业或其他事务没完成,我们想要一种手段来禁止孩子访问这些内容。问题在于,我们还施行在家教育,孩子们的教学内容大多也放在网上。所以,我们根本没法阻止孩子访问。我们需要更灵活一点的方法。

我着手解决这个问题时,列出了想要完成的几个目标:

1. 我不想让管理孩子的互联网访问成为一项全职工作。我想要能够制定策略,然后执行策略。

2. 我妻子不想知道如何登录、如何修改配置文件以及如何重启代理守护程序。她只需要运行浏览器、勾选几个方框就能完事。

3. 我不想编写太多的代码。我愿意编写一点点代码,但要是功能已经有了,我可没兴趣白费力气做重复工作。

4. 我想能够执行对我家来说很合理的几乎任何策略。

5. 如果有人将笔记本电脑带到外面去,我不希望影响他们访问互联网。

我确信并非只有我家才对这样的结果有兴趣。然而,我假设在其他企业组织可能没有意义的一幕:我的孩子不会采取任何高明的措施来规避我们制定的策略。然而,要是孩子果真规避策略,我确实保留针锋相对的权力。

为了撰写本文,只要这个假定导致配置在较复杂的环境下可能毫无意义,我就会试着讨论几个办法,好让你可以加强配置。

我找不到市面上哪一款软件包异常灵活,足以完成我想要实现的任务,又足够易于使用,那样我和妻子没必要花大力气来使用它。我明白,我只需要稍微编写一点代码,Squid代理服务器有望实现我想要实现的功能。我编写的代码会告诉代理服务器如何处理接到的每个请求。代理服务器会完成用户请求,或者向用户发送网页,表明用户试图访问的网站受到了阻挡。这就是代理将执行我们选择的任何策略的方式。

我认定,我希望能够为家人提供四种互联网访问级别当中的一种。在两种极端情况下,拥有“允许”访问权的家人可以随意访问网站,而访问权“被阻止”的家人无权随意访问网站。比如说,我和妻子就拥有“允许”访问权。要是禁止某个孩子上网,我们只要将他设为“阻止”状态。

然而,能够允许孩子只能访问事先确定的网站列表(比如说用于教学内容)也许是明智之举。这种情况下,我们需要“仅限白名单”访问级别。最后,我打算采用一种“过滤”的访问级别,那样我们在控制访问方面能做得更精细一点,阻止音乐下载、Flash游戏和Java小应用程序之类的内容。这是孩子通常拥有的访问级别。然后我们可以说“不再玩游戏”,让代理服务器执行该策略。

由于我不想为这一切编写一个实际的界面,于是就使用phpMyAdmin来更新数据库和设定策略(图1)。为了授予特定的访问级别,我只要更新格网中对应的单元,1表示开启,0表示关闭。

借助Squid代理服务器,建立灵活的访问控制系统

图1:更改访问策略的phpMyAdmin界面

执行策略还需要一番客户机配置,我稍后会加以讨论。然而,我还要讨论使用OpenDNS来过滤掉我不愿花时间来测试和过滤的内容。这是纵深防御体系的一个典例。

我配置了OpenDNS,过滤掉没想过要改变想法的内容。我认为我家没有任何理由能够访问约会网站、赌博网站或色情网站(图2)。虽然不尽如人意,但OpenDNS的人员在过滤这类内容方面做得相当好,我自己没必要做任何测试工作。

借助Squid代理服务器,建立灵活的访问控制系统

图2:OpenDNS可过滤掉显然不妥的内容

我在前面提到,这会需要一番客户机配置。大多数Web浏览器允许你配置浏览器,以便使用代理服务器来访问互联网。最简单的办法就是勾选复选框,开启代理访问功能。然而,要是我的孩子将笔记本电脑带到图书馆,而我们的代理系统在图书馆无法使用,他们就没法访问互联网,这就违反了第五个目标。于是,我决定使用大多数现代浏览器支持的自动代理配置。这就需要我编写一个JavaScript函数,确定如何来访问网站,是直接访问还是通过代理来访问(代码片段1)。

代码片段1:自动代理配置脚本

1 function FindProxyForURL(url, host) {

2

3 if (!isResolvable(“proxy.example.com”) {

4 return “DIRECT”;

5 }

6

7 if (shExpMatch(host, “*.example.com”)) {

8 return “DIRECT”;

9 }

10

11 if (isInNet(host, “10.0.0.0”, “255.0.0.0”)) {

12 return “DIRECT”;

13 }

14

15 return “PROXY 10.1.1.158:3128; DIRECT”;

16 }

每当浏览器访问网站,它会调用FindProxyForURL()函数,看看应该使用什么方法来访问网站:是直接访问,还是通过代理访问。代码片段1中显示的函数仅仅是个例子,不过它演示了几种值得一提的使用场合。从第15行可以看出,你能返回分号隔开的列表,该列表列出了使用的方法。浏览器会依次逐一尝试。在这种情况下,如果代理碰巧访问不了,你可以回退到直接访问该网站的办法。在比较严格的环境下,这可能不是正确的访问策略。

在第11行上,你能发现我确保可以在我家的局域网上直接访问网站。在第7行上,我演示了如何测试特定的主机名称。有几个网站我通过工作站上的VPN隧道来访问,所以我就无法使用代理服务器。最后在第3行上,你能看到蛮有意思的东西。这里,我测试查看某个主机名称是否可以解析成IP地址。我配置了局域网的DNS服务器来解析该名称,但是其他DNS服务器就无法解析。这样一来,孩子们将笔记本电脑带到家庭网络外面后,浏览器不会试着使用代理服务器。当然,我们完全可以切换故障、使用直接访问方法,就像我们在第15上行做的那样,但是故障切换需要时间。

比较懂行的用户有可能规避代理自动配置。针对不同浏览器的一些插件可以防止用户更改该配置。然而,这无法阻止用户安装新的浏览器或开启新的火狐配置文件。想执行该策略,万无一失的方法就是在网关路由器处执行:只要设定一条防火墙规则,阻止除代理服务器之外的任何IP地址访问互联网。如果需要的话,甚至可以为特定的客户机/主机组合来设定防火墙规则。

你给网关路由器添加防火墙规则时,可能忍不住想配置路由器,以便通过代理服务器转发所有的网络流量,组成经常所谓的透明代理服务器。然而,据RFC 3143认为,这并不是推荐的配置,因为它常常会扰乱浏览器缓存和历史记录等内容。

所以前面已讨论了客户机、DNS和可能的路由器配置,现在就该看一看Squid代理服务器的配置了。安装本身相当简单直观。我就使用了发行版的程序包管理系统,所以在此不作讨论。Squid代理服务器提供了可以开启的许多设置,以便优化缓存和互联网连接。尽管性能改进是实施代理服务器的一个附带好处,但是那些配置选项不在本文探讨范围之内。为了将我的代码接入到系统,只需要更改一处配置。只要编辑/etc/squid/squid.conf文件,添加下面这一行即可:

redirect_program /etc/squid/redirector.pl

这一个指令实际上告诉Squid代理服务器“询问”我的程序如何处理客户机提出的每个请求。

程序逻辑相当简单:

1. 侦听STDIN、接收请求。

2. 解析请求。

3. 根据策略做出决定。

4. 将答复返回给代理服务器。

不妨看一下代码片段2中的示例代码。

代码片段2:代理重定向器

1 #!/usr/bin/perl

2

3 use DBI;

4

5 $blocked = “http://192.168.1.10/blocked.html”;

6

7 my $dbh = DBI->connect(“dbi:mysql:authentication:host=

↪192.168.1.10”, “user”, “password”) || die(“Can\’t

↪connect to database.\n”);

8

9 $|=1;

10

11 while () {

12 my($sth, $r, $c);

13 my($url, $client, $d, $method, $proxy_ip, $proxy_port);

14

15 chomp($r = $_);

16

17 if ($r !~ m/\S+/) { next; }

18

19 ($url, $client, $d, $method, $proxy_ip, $proxy_port)

↪= split(/\s/, $r);

20

21 $client =~ s/\/-//;

22 $proxy_ip =~ s/myip=//;

23 $proxy_port =~ s/myport=//;

24

25 $sth = $dbh->prepare(“select * from web_clients

↪where ip=\’$client\'”);

26 $sth->execute();

27 $c = $sth->fetchrow_hashref();

28

29 if ($c->{blocked} eq “1”) {

30 send_answer($blocked);

31 next;

32 }

33

34 if ($c->{whitelist_only} eq “1”) {

35 if (!is_on_list(“dom_whitelist”, $url)) {

36 send_answer($blocked);

37 next;

38 }

39 }

40

41 if ($c->{filtered} eq “1”) {

42 if ($c->{games} eq “0”) {

43 # Check URL to see if it’s

↪on our games list

44 }

45

46 if ($c->{flash} eq “0”) {

47 # Check URL to see if it looks

↪like flash

48 }

49

50 send_answer($url);

51 next;

52 }

53

54 if ($c->{open} eq “1”) {

55 send_answer($url);

56 next;

57 }

58

59 send_answer($url);

60 next;

61 }

62

63 exit 0;

64

65 #############################################################

66

67 sub send_answer {

68 my($a) = @_;

69 print “$a\n”;

70 }

71

72 sub is_on_list {

73 my($list, $url) = @_;

74 my($o, @a, $i, @b, $b, $sth, $c);

75

76 $url =~ s/^https*:\/\///;

77 $url =~ s/^.+\@//;

78 $url =~ s/[:\/].*//;

79

80 @a = reverse(split(/\./, $url));

81

82 foreach $i (0 .. $#a) {

83 push(@b, $a[$i]);

84 $b = join(“.”, reverse(@b));

85

86 $sth = $dbh->prepare(“select count(*) from

↪$list where name=\’$b\'”);

87 $sth->execute();

88 ($c) = $sth->fetchrow_array();

89

90 if ($c > 0) { return $c; }

91 }

92

93 return $c+0;

94 }

95

主循环在第11行开始,它从STDIN读取。第11行至第24行主要涉及解析来自Squid代理服务器的请求。在第25行至第28行,程序查询数据库,看看某个客户机有什么样的权限。第29行至第57行是查看从数据库读取了什么样的权限,然后返回相应值。在客户机被允许“过滤”访问互联网的情况下,我在头脑中对逻辑有了轮廓。我不想本文纠缠于琐碎的代码。更重要的是演示Squid代理重定向系统的结构和一般逻辑,而不是提供完整代码。不过你会发现,我只要用短短几行代码和正则表达式,就能实施几乎任何想得到的访问策略。

从第67行开始的send_answer()函数其实眼下没有多大作用,不过以后,我可以在这里相当轻松地添加一些日志功能。

从第72行开始的is_on_list()函数也许有点意思。该函数拿来客户机试图访问的主机名称后,将它细分成子域列表。然后,它检查那些子域是否列在数据库中,数据库名称作为参数来传递。这样一来,我只要把example.com放入到数据库,它就会匹配example.com、www.example.com或webmail.example.com。

通过传递不同的表名称,我就能使用同样的匹配算法来匹配许多不同的访问控制列表。

正如你所见,代码其实并不是很复杂。但是由于添加了一点复杂性,我应该能够执行能想得到的几乎任何访问策略。然而,有一个方面需要加以改进。就处理的每个访问请求而言,程序要访问数据库好几次。这种操作的效率极其低下,等到你看到本文,我可能已经实施了某种缓存机制。

然而,缓存也会让系统不太迅即响应访问策略变更或访问控制列表,因为我不得不等待缓存信息过期或重启代理守护程序。

实际上,我见过值得一提的方法。大多数网站浏览器有各自的缓存机制。由于该缓存,如果你更改了代理服务器处的访问策略,客户机并不总是意识到变更。在“敞开”访问权的情况下,客户就需要刷新缓存,那样才能访问之前受阻的内容。在限制访问的情况下,该内容在缓存过期之前仍可能可用。一个解决办法就是,将本地缓存大小设为0,就依赖代理服务器的缓存。

此外,一旦客户机经过配置、与本地网络上的代理服务器对话,就有可能换入不同的代理服务器,或者甚至以菊花链方式连接代理服务器,而客户机不需要任何操作。这就带来了这种可能性:比如说,使用Dan’s Guardian进行内容过滤,另外还能控制访问。

至此,大家可能会想我在控制方面有点过于严格了。然而,我家人把大量时间花在网上――有时太过分了。大多数时候,我家人在适度使用互联网,但要是使用没节制,我和妻子需要有一种手段来执行家规,又没必要老是监控孩子们。

此条目发表在未分类分类目录。将固定链接加入收藏夹。