作者简介:朱颖骏,中通信息安全团队开发组组长,目前负责中通安全整体后端架构。熟悉多种编程语言,个人爱好研究各类开源产品。

背景

随着各类安全攻击愈发频繁,数据泄露现象也愈发严重,企业越来越关注内外部的安全威胁, 当前的一些安全产品的关注点大多为基于网络边界的安全防护, 而无法从本质上解决企业面临的安全问题。

企业面临的安全问题大致可分为三类:

  • 过度依赖基于网络边界的安全防护,无法建立基于身份的安全保护,用户体验较差

  • 无法在应用层做动态的安全防护,安全访问策略过于简单和静态化

  • 无法对业务的全流量进行实时的风控防护以及告警、审计和可视化

为了解决上述问题, 中通参考谷歌BeyondCorp打造了一套定制化的安全解决方案。下文主要描述安全访问代理模块。

设计目标

技术要求

1.作为Web应用的上层建筑, 能够直接对7层HTTP流量进行操作

2.对业务系统低耦合, 减少对业务系统的侵入性

3.易于扩展与维护, 可快速针对不同业务场景实现不同的策略

4.高性能, 需要在毫秒级完成访问控制

5.高并发, 需要的并发性能高于下游应用n倍, 以应对突发流量

6.高可用保障

7.与中通现有的IAM平台结合, 集中管理认证授权

功能要求

访问代理核心功能:

  • 认证: 为了认证一个请求, 访问代理需要识别发起请求的用户身份和设备信息

  • 授权: 采用简易DSL进行粗粒度的权限控制, 与应用侧细粒度权限控制结合组成完整的授权机制

  • 访问控制策略: 与后端访问控制引擎进行交互, 执行UEBA等模块的结果

  • 加密: 端到端TLS加密

  • 负载均衡

  • 自动故障恢复, 能处理诸如数据库, 依赖服务故障等问题, 为做到这点, 需周期性将核心数据快照至本地数据源

  • 简易Waf功能

  • 日志记录

实现方案

访问代理底层框架选型

Envoy:

C++编写的开源软件, 伴随Service Mesh模式兴起, 号称云原生, 但C++并不是我们团队擅长的, 而且项目的发展方向是sidecar而非网关, 故不考虑Envoy。

Nginx,OpenResty,kong:

三者的关系为, OpenResty基于Nginx, kong基于OpenResty。作为常见的反向代理或负载均衡软件, Nginx已成为Web开发者的必备技能之一。 OpenResty 是一个基于 Nginx 与 Lua 的高性能 Web 平台,其内部集成了大量精良的 Lua 库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态 Web 应用、Web 服务和动态网关。

Kong 核心基于OpenResty构建, 可通过Restful API对API,Service,Upstream等进行管理, 并拥有强大的插件扩展功能。

结合kong的插件机制, 可自由编写强目的性的安全插件, 如官网提供的Bot Detection,cors等, 另外Nginx强大的I/O模型使得其性能也有所保障, 所以kong最适合我们的场景。

架构设计

图1: 访问代理模块图

如上图, 访问代理分为两大部分Proxy和Local Daemon; 其中kong Proxy包含了认证模块、鉴权模块、访问控制模块、缓存模块、RPC通信模块和监控指标模块。

  • 用户、设备识别认证模块: 为验证一个请求, 访问代理需要识别发起请求的用户、设备信息, 与中通的统一认证(SSO)集成以完成该模块

  • 鉴权模块: 在该模块下, 对用户请求进行粗粒度的授权, 通过两种方式的组合

1.通过RPC调用查询集中的鉴权API

2.内置的简易DSL

  • 访问控制策略模块: 执行后端访问控制引擎生成的策略, 对各类异常进行处理

  • 缓存模块: 包括nginx的worker级内存缓存、Nginx节点级别的Shared memory cache与Redis缓存

  • RPC通信模块: 该模块使用微博开源的motan-openresty, 用于调用上述的认证鉴权等服务

  • 监控指标模块: 提供Promethues形式的指标, 用于记录各个过程成功失败数、各个过程耗时等指标数据

同时为了满足高可用需求, 我们设计开发了Local Daemon。以满足在极端情况下, Proxy依赖的服务不可用时, Proxy可降级至本地存储, 如若发生Proxy不可恢复的故障时, Local Daemon将直接将流量调控至下游服务避免服务不可用。

如下全览图, 值得注意的一点是, 访问代理在整个过程中对下游应用透明, 不对下游应用产生副作用, 下游应用不会感知到多余的cookie或其他凭据信息。

图2: 访问代理全览图

工程实践

我们利用kong的插件机制实现安全访问代理, 下面介绍一下kong的插件开发。

kong插件目录结构

Kong所有的插件都位于kong/plugins目录下, 如下在plugins目录我们创建一个名为custom-plugin, 目录结构及推荐命名如下。

图3: kong插件目录结构图

插件入口文件

下面我们来看插件的入口文件handler.lua, 你可以重写以下方法用以在kong的生命周期内执行想要的自定义逻辑。

图4: kong handlers.lua

如下表格, 为kong插件中支持重写的函数列表

函数名

Lua-Nginx-Module 上下文

描述

:init_worker()

init_worker_by_lua

在每个Nginx Worker启动时执行

:certificate()

ssl_certificate_by_lua_block

在SSL握手的SSL证书服务阶段执行

:rewrite()

rewrite_by_lua_block

每个请求中的rewrite阶段执行

:access()

access_by_lua

在被代理至上游服务前执行

:header_filter()

header_filter_by_lua

从上游服务器接收所有Response headers后执行

:body_filter()

body_filter_by_lua

从上游服务接收的响应主体的每个块时执行。 由于响应被流回客户端,因此它可以超过缓冲区大小并按块进行流式传输。 因此,如果响应很大,则会多次调用此方法

:log()

log_by_lua

当最后一个响应字节输出完毕时执行

另外还有一点, 插件的执行有时是依赖于特定的顺序的, 比如认证的插件应当先于部分业务插件执行, 可以在handler.lua中配置参数CustomHandler.PRIORITY 。

插件配置

图5: kong schema.lua

如上图

  • no_consumer: 如果为true, 则插件只能被应用于Service和Routes

  • fields: 一个field数组, field中可定义type,required,unique,default,immutable,enum,regex 等属性

  • self_check: 在安装时, 执行的自定义校验函数

PDK(Plugin Development Kit)

kong 的插件开发套件包含了一些常用的Lua函数和变量, 如kong.client,kong.request,kong.log,kong.db,kong.table 等。

数据访问

Kong自身存储可选PostgreSQL和Cassandra, 在开发插件时, kong提供了一个数据库抽象层用于存储自定义的实体, 也就是dao层。

要完成数据访问, 需要两步:

  • 编写Migration文件, 用于数据库DDL操作, 在kong migrations up时执行

  • 编写daos.lua, 用于映射你的数据表

如下为PostgreSQL示例:

图6: kong postgres.lua

图7: kong daos.lua

postgres.lua 由一个Migration数组组成, 每个Migration包含三个字段:name,up,down, 其中name须唯一, up和down分别代表升级和降级时执行的SQL语句。

完成以上两步后, 即可通过如下所示代码在我们的插件中对数据库进行操作。

图8: kong数据库开发示例

缓存

作为访问代理层, 缓存一定是必不可少的一环, 在kong的PDK中, 封装了lua-resty-mlcache。

kong的缓存分为两级:

  • L1: Lua memory cache - 在Nginx worker中共享, 可以存储任何Lua值

  • L2: Shared memory cache (SHM) - 在Nginx node的所有worker中共享. 可以存储任何标量值, 但它需要序列化和反序列化, 所以性能会有所下降

注: 从数据库中提取数据后,它将同时存储于上述两级缓存中。现在,如果同一个工作进程再次请求数据,它将从Lua内存缓存检索数据。 如果同一个Nginx节点中的不同工作者请求该数据,它将在SHM中找到数据,对其进行反序列化(并将其存储在自己的Lua内存缓存中),然后将其返回。

一个典型的使用方式如下:

图9: kong cache示例

函数: value, err = cache:get(key, opts?, cb, ...) , 如果cache没有值(miss), 则会调用函数cb, cb 必须返回一个返回值, 可返回需要缓存的值

或者nil, 需要注意的是返回值为nil时, get函数依旧会执行缓存, 但我们可以通过cache:get()的第二个参数控制缓存的TTL和negative TTL. 如下代码即代表有数据时, 我们缓存600s, 没有数据时, 缓存nil结果40s。

图10: kong cache get示例

通过上述API开发者可轻松实现缓存懒加载功能。

运行

在首次运行或更新代码时, 执行kong migrations up 对数据库进行DDL操作, 完成后启动。

图11: docker kong示例

在生产环境时, 若使用docker部署, 避免对性能产生影响, --network参数需设置为host。

Kong GUI

在管理平台上, 我们选择在konga的基础上实现了kong的插件管理功能, 并集成到中通的安全管理运营平台, 如下为两个常用的第三方kong Web UI。

图12: Konga示例

Konga https://github.com/pantsel/konga

图13: Kong dashboard示例

未来展望

作为中通零信任安全架构中的重要一环, 访问代理还需支持更多的协议解析, 如SSH、SFTP等, 保护所有的入口流量。另外访问代理将与中通其他安全产品做更深入的结合, 加强运营并输出更多的安全报告至自研的安全管理运营平台。

参考资料:

  • BeyondCorp at Google https://ai.google/research/pubs/pub43231.pdf
  • Kong Plugin Development https://docs.konghq.com/1.0.x/plugin-development
  • Docker OpenResty https://github.com/openresty/docker-openresty
  • Docker kong https://github.com/Kong/docker-kong
  • Github kong https://github.com/Kong/kong
  • lua-resty-mlcache https://github.com/thibaultcha/lua-resty-mlcache

团队介绍

中通信息安全团队是一个年轻、向上、踏实以及为梦想而奋斗的大家庭,我们的目标是构建一个基于海量数据的全自动信息安全智能感知响应系统及管理运营平台。我们致力于支撑中通快递集团生态链全线业务(快递、快运、电商、传媒、金融、航空等)的安全发展。我们的技术栈紧跟业界发展,前有 React、Vue,后到 Golang、Hadoop、Spark、TiDB、AI 等。全球日均件量最大快递公司的数据规模也将是一个非常大的挑战。我们关注的方向除了国内一线互联网公司外,也关注 Google、Facebook、Amazon 等在基础安全、数据安全等方面的实践。

声明:本文来自中通安全应急响应中心,版权归作者所有。文章内容仅代表作者独立观点,不代表安全内参立场,转载目的在于传递更多信息。如有侵权,请联系 anquanneican@163.com。