实现目标

随着云计算的发展,安全方面得到越来越多的重视,但云安全这块领域应该才算刚起步,目前各个传统安全厂商将各自的安全产品云化也只是将镜像抠出来部署进云环境,然后使用传统的处理方式将流量引入安全设备进行处理。

可行性方案详见:openstack中引入firewall镜像方案概述

目前采用的方案实现原理详见: openstack中引入firewall镜像——租户网络旁挂引流回注,这里将实现目标再次进行说明。

使用类似于“物理旁挂,逻辑串联”的方式旁挂在 qrouter 旁。
根据用户的需求,是否使用 PBR 将指定 subnet 流量引入 FW,需要则在 qrouter 的 namespace 写入 iptables 策略,不需要则删掉策略。

拓扑如下图所示:
租户网络内部L3部署——旁挂引流回注

即完成以下目标:

  • 可以使用 360 的镜像启动虚机实例提供 firewall 功能
  • 将同 router 下的 vm 子网流量引向 firewall (policy-based routing)

架构设计

根据neutron的结构,该neutron-uplugin项目按照service plugin 架构去实现,以service_plugin的配置项被neutron加载。这样可以通过配置文件去管理该插件是否进行加载,这样去做避免了keystone,api框架的设计,同时也符合neutron项目对高级功能的实现。

neutron-uplugin实现架构

neutron-server: 在配置文件中指定uplugin后,neutron-server会加载uplugin中的resource, 也就是 firewall 和 policyrouting。这样neutron-server就可以将与该几种资源相关的API路由到我们的方法上,从而实现相应的功能, 在前端我们需要单独的发送API来实现该功能。

uplugin:主要负责数据库的读写,并且与agent之间进行RPC通信。

agent:调用driver实现功能, 同时实现与plugin之间的RPC通信。

driver:具体的去实现相应的功能。

需要进行补充说明的是,firewall 模块虽然采用自服务模式,但并不向租户暴露虚机实体,即 firewall 虚机建立在 admin_project 下,向用户暴露IP地址。

因此,uplugin需要从配置文件读取 admin 的权限,另外对于镜像和其 flavor 也在配置文件里:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/etc/neutron/neutron.conf

[service_auth]
memcached_servers = 192.168.100.15:11211
# cafile = /opt/stack/data/ca-bundle.pem
project_domain_name = Default
project_name = admin
user_domain_name = Default
password = ustack
username = admin
auth_type = password
auth_url = http://192.168.100.15/identity

[firewall]
image_id = 2bd36666-23f3-4116-bbf8-05d99ce6ed53
flavor_id = d2

数据库设计

项目的整体设计参照neutron service plugin(neutron-lbaas, neutron-fwaas, neutron-vpnaas) 的架构设计,故在数据库的设计上,也同service plugin 一样,在neutron 的数据库中插入表,利用alembic单独的控制该项目表的版本。目前在数据库中插入了gwratelimits 和fipratelimits表,还有一个进行版本控制的alembic_version_fiplimit 表(记录版本号)。在升级的时,采用neutron-db-manage 加载该子项目去升级。

注:由于RBAC,某些字段进行了冗余。一些级联操作也待改进。

E-R图

neutron引流E-R图

这是最终的模型。vm_subnet 和 fw_subnet 这两张表是为了对建立 firewall 和 进行引流的 subnet 进行记录和统计,以便对网络引流拓扑进行更新操作。目前没有这两张表,即不支持 update 操作,firewall 和 fw_subnet(资源)也是 1:1 的关系。

firewall

为了避免和 fwaas 的表产生冲突,表名改为 firewalls_360

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
+------------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+-------+
| project_id | varchar(255) | YES | | NULL | |
| id | varchar(36) | NO | PRI | NULL | |
| name | varchar(255) | YES | | NULL | |
| router_id | varchar(36) | NO | MUL | NULL | |
| subnet_id | varchar(36) | NO | UNI | NULL | |
| router_port_id | varchar(36) | NO | UNI | NULL | |
| instance_port_id | varchar(36) | NO | MUL | NULL | |
| instance_id | varchar(36) | YES | UNI | NULL | |
| subnet_cidr | varchar(64) | YES | UNI | NULL | |
| ip_address | varchar(15) | YES | | NULL | |
| status | varchar(16) | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| update_at | datetime | NO | | NULL | |
| description | varchar(255) | YES | | NULL | |
+------------------+--------------+------+-----+---------+-------+

policyrouting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
+--------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+--------------+--------------+------+-----+---------+-------+
| project_id | varchar(255) | YES | | NULL | |
| id | varchar(36) | NO | PRI | NULL | |
| name | varchar(255) | YES | | NULL | |
| vm_subnet_id | varchar(36) | NO | UNI | NULL | |
| vm_port_id | varchar(36) | YES | UNI | NULL | |
| gw_port_id | varchar(36) | YES | | NULL | |
| firewall_id | varchar(36) | NO | MUL | NULL | |
| status | varchar(16) | YES | | NULL | |
| created_at | datetime | NO | | NULL | |
| update_at | datetime | NO | | NULL | |
| description | varchar(255) | YES | | NULL | |
+--------------+--------------+------+-----+---------+-------+

业务逻辑

firewall

create
  1. 前端提供 firewall 的 name,承载 firewall 的子网的 cidr,和执行引流策略的 router_id
    注:如果可以的话,前端需要对 cidr 进行判断,将租户已存在的 cidr 和 fw_subnet 已经用过的(通过 GET API)设为非法。
  2. 在 admin_project 下建立 network 和指定 cidr 的 subnet
  3. 启动一个 firewall 实例,并分配取消安全组和端口安全的 port,再连接上指定的 router
get(复数)

返回如下字段:
router_id/ip_address/status/created_at/update_at/description

get
  1. 前端指定 firewall_id
  2. 返回如下字段:
    router_id/ip_address/status/created_at/update_at/description
    protected_subnet:需要通过 pbr 复数资源的 GET API 获取,再通过 firewall_id 字段过滤,显示出来的资源关联至租户的 subnet 资源。
delete

前端指定 firewall_id,并进行 pbr 的复数资源 GET 操作,相应 firewall_id 无记录,才能进行。

policy-based routing

create
  1. 前端点进 firewall 资源信息,建立 pbr
  2. 前端提供选择可以在当前 firewall 下发引流策略的 subnet,即过滤出与 firewall 连接同一 router 的 subnet
  3. 前端提供 firewall_id 和 vm_subnet_id
  4. 后端根据给出信息获取如下信息
    firewall_id –> [route_id, fw_port_id, fw_subnet_id, firewall_ip]
    route_id –> [gw_port_id]
    vm_subnet_id –> [vm_port_id]
  5. 在 router 的 namespace 中下发引流策略,详细策略请参见:引流策略
    注:这里采用了 taskflow 库,保证策略下发和删除的完整性,避免异常导致流量断掉。
get(复数)

返回 vm_subnet_id 字段:并不显示,直接关联至租户的 subnet 资源。

delete

前端指定 pbr_id,进行删除操作。

API设计

firewall

POST /v2.0/uplugin/firewalls

1
curl -g -i -X POST http://192.168.100.15:9696/v2.0/uplugin/firewalls -H "X-Auth-Token: gAAAAABbWEjdihEIOvcGHJG-frz80F1thD9FCwn3nhMd3FqIBiSIQAV60RK_yi5Nok36btlUG-D3Kn-NID4EzgeRa2OLJhk-B6qT1-ehJEBJQEc96ewNOdDExVQfV48c3Hmi8TvryE4a1Z1IrUqr8Vp_XIELcSLh344jt0pCEAfUjDq8B09yE80" -d '{"firewall": {"name":"360","router_id":"8c16e162-37a2-469a-b77d-42b3c49e3db8","subnet_cidr":"192.168.139.0/24"}}'

GET /v2.0/uplugin/firewalls

1
curl -g -i -X GET http://192.168.100.15:9696/v2.0/uplugin/firewalls -H "X-Auth-Token: gAAAAABbYtJAK1A9_nOlAvTsleUI6B88lxCakChpAgw64q6jFzLmO_3rVda2QIK3AE9r8QcFbVXorsUuYcb-hmPqFM4a5_kUJjuMFPZHvBY-MhpNRk6YuU-8nWeQQil8UV56rxOBEfi_6whWbi4IzEfq-8cHG1s4RVkdfqVHXEnQGVE43n_VrVM"

GET /v2.0/uplugin/firewalls/id

1
curl -g -i -X GET http://192.168.100.15:9696/v2.0/uplugin/firewalls/99def6cc-984e-4bc3-8c4a-1d5f9736da21 -H "X-Auth-Token: gAAAAABbYtJAK1A9_nOlAvTsleUI6B88lxCakChpAgw64q6jFzLmO_3rVda2QIK3AE9r8QcFbVXorsUuYcb-hmPqFM4a5_kUJjuMFPZHvBY-MhpNRk6YuU-8nWeQQil8UV56rxOBEfi_6whWbi4IzEfq-8cHG1s4RVkdfqVHXEnQGVE43n_VrVM"

DELETE /v2.0/uplugin/firewall/id

1
curl -g -i -X DELETE http://192.168.100.15:9696/v2.0/uplugin/firewalls/99def6cc-984e-4bc3-8c4a-1d5f9736da21 -H "X-Auth-Token: gAAAAABbYtJAK1A9_nOlAvTsleUI6B88lxCakChpAgw64q6jFzLmO_3rVda2QIK3AE9r8QcFbVXorsUuYcb-hmPqFM4a5_kUJjuMFPZHvBY-MhpNRk6YuU-8nWeQQil8UV56rxOBEfi_6whWbi4IzEfq-8cHG1s4RVkdfqVHXEnQGVE43n_VrVM"

policy_based_routing

POST /v2.0/uplugin/policyroutings

1
curl -g -i -X POST http://192.168.100.15:9696/v2.0/uplugin/policyroutings -H "X-Auth-Token: gAAAAABbXsqe9PQrpexezU8bl02zK-Kil3PBh2uZIn11S0NWI3Vf164_KoCAxprr_1hVSZSzSuyj1kZyAplJ7SmZ9CvmwBAYd06-DSvxcaNy2t5LWbNtyA8BLhEd02sjnO3gQtWCWu37C19BywcpcgvbIy57BRnqL2aeoPGyC330b1_v0gjrlxA" -d '{"policyrouting": {"name": "test", "firewall_id": "18ac59a4-0925-4bfc-8dcf-c3dc9179cba6", "vm_subnet_id": "4f29ab77-e7da-4189-a23a-819af522ae51"}}'

GET /v2.0/uplugin/policyroutings

1
curl -g -i -X GET http://192.168.100.15:9696/v2.0/uplugin/policyroutings -H "X-Auth-Token: gAAAAABbYtJAK1A9_nOlAvTsleUI6B88lxCakChpAgw64q6jFzLmO_3rVda2QIK3AE9r8QcFbVXorsUuYcb-hmPqFM4a5_kUJjuMFPZHvBY-MhpNRk6YuU-8nWeQQil8UV56rxOBEfi_6whWbi4IzEfq-8cHG1s4RVkdfqVHXEnQGVE43n_VrVM"

DELETE /v2.0/uplugin/policyrouting/id

1
curl -g -i -X DELETE http://192.168.100.15:9696/v2.0/uplugin/policyroutings/1511d675-e311-4649-8047-5a9db073f0b4 -H "X-Auth-Token: gAAAAABbYtJAK1A9_nOlAvTsleUI6B88lxCakChpAgw64q6jFzLmO_3rVda2QIK3AE9r8QcFbVXorsUuYcb-hmPqFM4a5_kUJjuMFPZHvBY-MhpNRk6YuU-8nWeQQil8UV56rxOBEfi_6whWbi4IzEfq-8cHG1s4RVkdfqVHXEnQGVE43n_VrVM"