前面几节介绍了整体架构和Open×××/IPtables的主要方向。本节将会增加一些干货,具体来分析Connect/Disconnect脚本以及相关的配置数据库的设计,跑通整个登录和访问权限设置的流程。

1首先,介绍一下connect/disconnect能够使用的环境变量。完整的环境变量列表可以manopen***,然后查看Environmental Variables这一段的内容。这里我挑了一些常用的和特别需要关注的。


bytes_received:接收到的字节数

bytes_sent:发送的字节数

这两个变量可以用来作为open***计费用。你可以很容易的计算一个用户的流量,唯一不方便的是,这个值只有在disconnect之后才能够获取到。

common_name:X509格式的用户名

ifconfig_pool_remote_ip:客户端获得的地址

password:用户密码

trusted_ip:客户端请求登陆时的地址

username:用户名

你需要在connect/disconnect脚本里面使用${Variable name}来调用相关的参数。

2一个简单的配置数据库的结构说明如下。

wKiom1LSJUqi9DMAAAJNI4AfPrQ288.jpg

user/user_group_names/network_group_names/port_group_names定义各种名字;user_group/network_group/port_group定义相关的组;Rules表,定义具体规则。

相关的字段的意义都比较直白。

建表脚本如下:





open***.sql

CREATE DATABASE`open***`;

USE `open***`;

CREATE TABLE `user`(

`user_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`user_name`    varchar(255) NOT NULL,

`full_name`    varchar(255) NOT NULL,

PRIMARY    KEY (`user_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `user_group_names`(

`user_group_name_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`user_group_name`    varchar(255) NOT NULL,

PRIMARY    KEY (`user_group_name_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `user_group`(

`user_group_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`user_group_name_id`    bigint(20) NOT NULL,

`user_id`    bigint(20) NOT NULL,

PRIMARY    KEY (`user_group_id`),

UNIQUE    KEY `user_group_uq` (`user_group_name_id`,`user_id`),

FOREIGN    KEY `user_group_user_id_fkey` (`user_id`) REFERENCES `user`(`user_id`),

FOREIGN    KEY `user_group_user_group_name_id_fkey` (`user_group_name_id`) REFERENCES    `user_group_names`(`user_group_name_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `network_group_names`(

`network_group_name_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`network_group_name`    varchar(255) NOT NULL,

PRIMARY    KEY (`network_group_name_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `network_group`(

`network_group_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`network_group_name_id`    bigint(20) NOT NULL,

`network`    varchar(15) NOT NULL,

`netmask`    int NOT NULL,

PRIMARY    KEY (`network_group_id`),

UNIQUE    KEY `network_group_uq` (`network_group_name_id`,`network`,`netmask`),

FOREIGN    KEY `network_group_network_group_name_id_fkey` (`network_group_name_id`)    REFERENCES `network_group_names`(`network_group_name_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `port_group_names`(

`port_group_name_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`port_group_name`    varchar(255) NOT NULL,

PRIMARY    KEY (`port_group_name_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `port_group`(

`port_group_id`    bigint(20) NOT NULL AUTO_INCREMENT,

`port_group_name_id`    bigint(20) NOT NULL,

`port_type`    enum('TCP','UDP','ICMP'),

`start_port`    int NOT NULL,

`end_port`    int NOT NULL,

PRIMARY    KEY (`port_group_id`),

UNIQUE    KEY `port_group_uq`    (`port_group_name_id`,`port_type`,`start_port`,`end_port`),

FOREIGN    KEY `port_group_network_group_name_id_fkey` (`port_group_name_id`)    REFERENCES `port_group_names`(`port_group_name_id`)

)ENGINE=InnoDB DEFAULT CHARSET=utf8;


CREATE TABLE `rules` (

`rule_id` bigint(20) NOT NULL AUTO_INCREMENT,

`user_group_name_id` bigint(20) NOT NULL,

`network_group_name_id` bigint(20) NOT NULL,

`port_group_name_id` bigint(20) NOT NULL,

PRIMARY KEY (`rule_id`),

UNIQUE KEY `rule_uq` (`user_group_name_id`,`network_group_name_id`,`port_group_name_id`),

FOREIGN KEY `rule_user_group_name_fkey` (user_group_name_id)    REFERENCES user_group_names(user_group_name_id),

FOREIGN    KEY `rule_network_group_name_fkey` (network_group_name_id) REFERENCES    network_group_names(network_group_name_id),

FOREIGN KEY `port_network_group_name_fkey` (port_group_name_id)    REFERENCES port_group_names(port_group_name_id)

) ENGINE=InnoDB DEFAULT CHARSET=utf8;



为了能够对系统进行测试,我们插入一些测试数据。

INSERT INTO `port_group_names` VALUES

(1,'RemoteControl'),

(2,'Web');


INSERT INTO `port_group` VALUES

(1,1,'TCP',22,22),

(2,1,'TCP',3389,3389),

(3,2,'TCP',80,80),

(4,2,'TCP',443,443);



INSERT INTO `network_group_names`    VALUES

(1,'AllProduct'),

(2,'WebSite');


INSERT INTO `network_group` VALUES

(1,1,'10.0.0.0',8),

(2,2,'10.1.1.0',24),

(3,2,'10.1.2.0',24);


INSERT INTO `user` VALUES

(1,'admin','Admin User'),

(2,'testuser','Test User');


INSERT INTO `user_group_names` VALUES

(1,'admin'),

(2,'user');


INSERT INTO `user_group` VALUES

(1,1,1),

(2,2,2);


INSERT INTO `rules` VALUES

(1,1,1,1),

(2,1,1,2),

(3,2,2,2);




3 系统初始化。系统启动时,IPTABLES需要先初始化相关的规则。这样当用户登录时,只需要把该用户的IP地址加入到UserGroup Chain中即可。本例仅仅使用Bash来实现这一功能,Bash对于Mysql的操作界面不是很友好,你也可以使用其它语言例如perl python或者C来实现。

#!/bin/sh


#首先需要清理自定义的所有Chain;清理Chain是有顺序要求的,引用其它Chain的Chain必须首先被清除,否则被引用的Chain无法清除;本例的顺序为清空FORWARD链->清空和删除User Group相关链->清空和删除Rule相关链->清空和删除Port相关链

echo "Clean    All Chains"

iptables -F FORWARD

iptables –I FORWARD    –j DROP

user_group_chains=(`iptables    -L -n | grep 'Chain user_group_' | awk '{print $2}'`)

let    "user_group_chain_length=${#user_group_chains[@]}"

for ((    user_group_chain_idx=0 ; user_group_chain_idx<$user_group_chain_length ;    user_group_chain_idx++ ))

do

user_group_chain_name=${user_group_chains[$user_group_chain_idx]}

iptables -F $user_group_chain_name

iptables -X $user_group_chain_name

done

echo    ">>Clean "$user_group_chain_length" Chains"


rule_chains=(`iptables    -L -n | grep 'Chain rule_' | awk '{print $2}'`)

let "rule_chain_length=${#rule_chains[@]}"

for ((    rule_chain_idx=0 ; rule_chain_idx<$rule_chain_length ; rule_chain_idx++    ))

{

rule_chain_name=${rule_chains[$rule_chain_idx]}

iptables -F $rule_chain_name

iptables -X $rule_chain_name

}

echo    ">>Clean "$rule_chain_length" Chains"


port_group_chains=(`iptables    -L -n | grep 'Chain port_group_' | awk '{print $2}'`)

let    "port_group_chain_length=${#port_group_chains[@]}"

for ((    port_group_chain_idx=0 ; port_group_chain_idx<$port_group_chain_length ;    port_group_chain_idx++ ))

{

port_group_chain_name=${port_group_chains[$port_group_chain_idx]}

iptables -F $port_group_chain_name

iptables -X $port_group_chain_name

}

echo    ">>Clean "$rule_chain_length" Chains"


#初始化各个Port Chain

#获取Port    List

echo "Init    Port Chains"

port_list=(`mysql    -uroot open***<<EOF

select    port_group_name_id,port_type,start_port,end_port from port_group order by    port_group_name_id;

EOF`);

let    "port_idx_length=${#port_list[@]}/4"

for (( port_idx=1 ;    port_idx<$port_idx_length ; port_idx++ ))

#mysql命令返回的第一行是列名,需要取除

do

let "port_group_name_id_idx=(    $port_idx * 4 )"

let "port_type_idx=( $port_idx * 4 )    + 1"

let "start_port_idx=( $port_idx * 4 )    + 2"

let "end_port_idx=( $port_idx * 4 ) +    3"

port_group_name_id=${port_list[$port_group_name_id_idx]}

port_type=${port_list[$port_type_idx]}

start_port=${port_list[$start_port_idx]}

end_port=${port_list[$end_port_idx]}

port_group_chain_name="port_group_"$port_group_name_id

iptables -L $port_group_chain_name -n    1>/dev/null 2>/dev/null

if [ $? == 1 ]

then

#当chain不存在时,创建之,默认处理方式为DROP

echo ">>Create The Port Group    Chain "$port_group_chain_name

iptables -N $port_group_chain_name

iptables -I $port_group_chain_name -j    DROP

fi

#插入规则

iptables -I $port_group_chain_name -p    $port_type --dport=$start_port:$end_port -j ACCEPT

done


#初始化Rule Chain

#获取Rule    List

echo "Init    Rule Chains"

rule_list=(`mysql    -uroot open***<<EOF

select    rule_id,network,netmask,port_group_name_id,user_group_name_id from rules    a,network_group b where a.network_group_name_id=b.network_group_id order by    user_group_name_id

EOF`);

let    "rule_idx_length=${#rule_list[@]}/5"

for (( rule_idx=1 ;    rule_idx<rule_idx_length ; rule_idx++ ))

#mysql命令返回的第一行是列名,需要取除

do

let "rule_id_idx=( $rule_idx * 5    )"

let "network_idx=( $rule_idx * 5 ) +    1"

let "netmask_idx=( $rule_idx * 5 ) +    2"

let "port_group_name_id_idx=(    $rule_idx * 5 ) + 3"

let "user_group_name_id_idx=(    $rule_idx * 5 ) + 4"

rule_id=${rule_list[$rule_id_idx]}

network=${rule_list[$network_idx]}

netmask=${rule_list[$netmask_idx]}

port_group_name_id=${rule_list[$port_group_name_id_idx]}

user_group_name_id=${rule_list[$user_group_name_id_idx]}

port_group_chain_name="port_group_"$port_group_name_id

user_group_chain_name="user_group_"$user_group_name_id

rule_chain_name="rule_"$rule_id

iptables -L $rule_chain_name -n    1>/dev/null 2>/dev/null

if [ $? == 1 ]

then

#当Rule chain不存在时,创建之,默认处理方式为DROP

echo ">>Create The Rule Chain    "$rule_chain_name

iptables -N $rule_chain_name

iptables -I $rule_chain_name -j DROP

iptables -L $user_group_chain_name -n    1>/dev/null 2>/dev/null

if [ $? == 1 ]

then

#当User Group chain不存在时,创建之,默认处理方式为DROP

echo    ">>Create The User Group Chain "$user_group_chain_name

iptables -N $user_group_chain_name

iptables -I $user_group_chain_name -j    DROP

fi

iptables -I $user_group_chain_name -j    $rule_chain_name

fi

#插入规则

echo ">>>>Insert Rule To    Chain "$rule_chain_name

iptables -I $rule_chain_name -s    $network/$netmask -j $port_group_chain_name

done



这时我们可以看一下IPTABLES创建的规则列表:

我们可以看到此时FORWARD Chain应该只有一条DROP的默认规则。

Chain FORWARD (policy ACCEPT)

targetprot opt sourcedestination

DROPall--0.0.0.0/00.0.0.0/0


Chain port_group_1 (1 references)

targetprot opt sourcedestination

ACCEPTtcp--0.0.0.0/00.0.0.0/0tcp dpt:3389

ACCEPTtcp--0.0.0.0/00.0.0.0/0tcp dpt:22

DROPall--0.0.0.0/00.0.0.0/0


Chain port_group_2 (2 references)

targetprot opt sourcedestination

ACCEPTtcp--0.0.0.0/00.0.0.0/0tcp dpt:443

ACCEPTtcp--0.0.0.0/00.0.0.0/0tcp dpt:80

DROPall--0.0.0.0/00.0.0.0/0


Chain rule_1 (1 references)

targetprot opt sourcedestination

port_group_1all--10.0.0.0/80.0.0.0/0

DROPall--0.0.0.0/00.0.0.0/0


Chain rule_2 (1 references)

targetprot opt sourcedestination

port_group_2all--10.0.0.0/80.0.0.0/0

DROPall--0.0.0.0/00.0.0.0/0


Chain rule_3 (1 references)

targetprot opt sourcedestination

port_group_2all--10.1.1.0/240.0.0.0/0

DROPall--0.0.0.0/00.0.0.0/0


Chain user_group_1 (0 references)

targetprot opt sourcedestination

rule_2all--0.0.0.0/00.0.0.0/0

rule_1all--0.0.0.0/00.0.0.0/0

DROPall--0.0.0.0/00.0.0.0/0


Chain user_group_2 (0 references)

targetprot opt sourcedestination

rule_3all--0.0.0.0/00.0.0.0/0

DROPall--0.0.0.0/00.0.0.0/0



4 Connect

前面介绍了IPTables初始化的工作。我们在这里假设用户已经正常通过验证,开始connect脚本了。


#!/bin/bash

username=${common_name}

tunnelip=${ifconfig_pool_remote_ip}

[    "x$username" == 'x' ] && exit 0

[    "x$tunnelip" == 'x' ] && exit 0


#首先需要清理自定义的所有该IP相关Chain

echo "Clean    All Chains"

user_chains=(`iptables    -L -n | grep $tunnelip | awk '{print $1" "$4}'`)

let    "user_chain_length=${#user_chains[@]}/2"

for ((    user_chain_idx=0 ; user_chain_idx < $user_chain_length ;    user_chain_idx++ ))

do

let    "chain_name_idx=user_chain_idx * 2"

let "ip_idx=( user_chain_idx *    2 ) + 1 "

chain_name=${user_chains[$chain_name_idx]}

ip=${user_chains[$ip_idx]}

iptables -D FORWARD -s $ip/32 -j    $chain_name

done


#创建用户相关规则

user_groups=(`mysql    -uroot open***<<EOF

select    user_group_name_id from user a,user_group b where    a.user_name="$username" and b.user_id=a.user_id

EOF`)

let    "user_group_length=${#user_groups[@]}"

for ((    user_group_idx=1 ; user_group_idx<$user_group_length ; user_group_idx++    ))

do

user_group_chain_name="user_group_"${user_groups[$user_group_idx]}

iptables -I FORWARD -s $tunnelip/32    -j $user_group_chain_name

done



假设我们以admin用户登录,Open×××分配的IP地址为172.18.1.1。用户登录Open×××

之后,IPTABLES的FILTER/FORWARD Chain增加如下规则:


Chain FORWARD    (policy ACCEPT)

targetprot opt sourcedestination

user_group_1 all -- 172.18.1.10.0.0.0/0

DROP all -- 0.0.0.0/0 0.0.0.0/0

5 Disconnect

用户正常登出后,应该清除用户所属IP的相关规则,这实际上是connect脚本的上半段。


#!/bin/bash

username=${common_name}

tunnelip=${ifconfig_pool_remote_ip}

[    "x$username" == 'x' ] && exit 0

[    "x$tunnelip" == 'x' ] && exit 0


#清理自定义的所有该IP相关Chain

echo "Clean    All Chains"

user_chains=(`iptables    -L -n | grep $tunnelip | awk '{print $1" "$4}'`)

let    "user_chain_length=${#user_chains[@]}/2"

for ((    user_chain_idx=0 ; user_chain_idx < $user_chain_length ;    user_chain_idx++ ))

do

let    "chain_name_idx=user_chain_idx * 2"

let "ip_idx=( user_chain_idx *    2 ) + 1 "

chain_name=${user_chains[$chain_name_idx]}

ip=${user_chains[$ip_idx]}

iptables -D FORWARD -s $ip/32 -j    $chain_name

done

前面用户登出之后,FORWARD Chain中的记录被清空:


Chain FORWARD    (policy ACCEPT)

targetprot opt sourcedestination

DROP all -- 0.0.0.0/00.0.0.0/0

本节详细介绍了Connect/Disconnect动作的所有操作。你还可以在Connect和Disconnect脚本中插入流量或者计费的功能,这里我就不赘述了。所有脚本都在附件的压缩包里。