开源API网关Orange源码分析

首先,了解一下Orange,Orange 是一个基于 OpenResty 的API网关。除 Nginx 的基本功能外,它还可用于API监控、访问控制(鉴权、WAF)、流量筛选、访问限速、AB测试、静/动态分流 等。 ​

说句实在的,它已经实现了绝大部分的功能,只不过目前已经处于停滞状态了

项目目录结构

  • api* 目测是提供的接口,官方文档中提到了该项目提供了API接口用于实现第三方服务
  • bin* 应该是运行目录,里面主要是lua的一些第三方包
  • conf* 配置模板,类比nginx中的配置
  • dashboard* 控制台程序
  • docs* 运行文档
  • install* 安装方法,包含了sql语句和安装执行脚本
  • orange* 也是一些Lua的代码,主要是核心代码所在地
  • rockspec* 似乎是对该项目的打包,类似于java的jar一样,相当于给别人直接使用
  • test* 测试库

代码分析

启动Orange

初始化

先贴代码再分析

-- 执行过程:
-- 加载配置
-- 实例化存储store
-- 加载插件
-- 插件排序
function Orange.init(options)options = options or {}local store, configlocal status, err = pcall(function()local conf_file_path = options.configconfig = config_loader.load(conf_file_path)store = require("orange.store.mysql_store")(config.store_mysql)loaded_plugins = load_node_plugins(config, store)ngx.update_time()config.orange_start_at = ngx.now()end)if not status or err thenngx.log(ngx.ERR, "Startup error: " .. err)os.exit(1)endlocal consul = require("orange.plugins.consul_balancer.consul_balancer")consul.set_shared_dict_name("consul_upstream", "consul_upstream_watch")Orange.data = {store = store,config = config,consul = consul}-- init dns_clientassert(dns_client.init())return config, store
end 

不得不感叹,lua里面的匿名函数用得可针对,将方法直接作为函数的参数的方式,对编译器的词法分析器和语法分析器带来的挑战吧,有机会一定要拜读一下Lua的编译器。 首先,读取mysql的配置,然后加载配置的所有插件。 ​

其余的内容,就是定义的插件的基本方法的调用

插件定义的关键代码

对于插件,一共有下面的几种方法

  • redirect()
  • rewrite()
  • access()
  • balance()
  • header_filter()
  • body_filter()
  • log()

也就是重定向,重写,接收(正常处理),平衡,请求响应头处理,内容处理以及日志处理。 ​

rewrite

redirect方法,默认的实现为:

function BasePlugin:rewrite()ngx.log(ngx.DEBUG, " executing plugin \"", self._name, "\": rewrite")
end 

只是用ngx.log打印出了一个日志。 我们随便拎一个实现了该方法的插件子类来看看,这里以headers插件为例:

function HeaderHandler:rewrite(conf)HeaderHandler.super.rewrite(self)local enable = orange_db.get("headers.enable")local meta = orange_db.get_json("headers.meta")local selectors = orange_db.get_json("headers.selectors")local ordered_selectors = meta and meta.selectorsif not enable or enable ~= true or not meta or not ordered_selectors or not selectors thenreturnendfor i, sid in ipairs(ordered_selectors) dolocal selector = selectors[sid]ngx.log(ngx.INFO, "==[Headers][START SELECTOR:", sid, "][NAME:",selector.name,']')if selector and selector.enable == true thenlocal selector_passif selector.type == 0 then -- 全流量选择器selector_pass = trueelseselector_pass = judge_util.judge_selector(selector, "headers")-- selector judgeendif selector_pass thenif selector.handle and selector.handle.log == true thenngx.log(ngx.INFO, "[Headers][PASS-SELECTOR:", sid, "]")endlocal stop = filter_rules(sid, "headers")if stop then -- 不再执行此插件其他逻辑returnendelseif selector.handle and selector.handle.log == true thenngx.log(ngx.INFO, "[Headers][NOT-PASS-SELECTOR:", sid, "] ")endend-- if continue or break the loopif selector.handle and selector.handle.continue == true then-- continue next selectorelsebreakendendend
end 

通过看源代码,可以发现,这里的一个个插件,优先像Java里面的过滤器或者PHP里面的中间件,利用装饰者模式,在外面不断套壳。 然后这里调用了处理逻辑函数filter_rules

local function filter_rules(sid, plugin)local rules = orange_db.get_json(plugin .. ".selector." .. sid .. ".rules")if not rules or type(rules) ~= "table" or #rules <= 0 thenreturn falseendfor i, rule in ipairs(rules) doif rule.enable == true then-- judge阶段local pass = judge_util.judge_rule(rule, "headers")-- handle阶段if pass then-- extract阶段headers_util:set_headers(rule)endendendreturn false
end 

如果满足条件,就会写入headers, 接着让我们回到Orange的主代码中看看rewrite方法是如何在上下文中被使用的。

function Orange.rewrite()ngx.ctx.ORANGE_REWRITE_START = now()for _, plugin in ipairs(loaded_plugins) doplugin.handler:rewrite()endlocal now_time = now()ngx.ctx.ORANGE_REWRITE_TIME = now_time - ngx.ctx.ORANGE_REWRITE_STARTngx.ctx.ORANGE_REWRITE_ENDED_AT = now_time
end 

令人吃惊的是,主代码就是循环调用每个插件的rewrite方法。这就会带来一个问题,两个插件如果作用于同一个属性,那么后执行的插件就会覆盖先执行的插件的结果。

redirect

默认实现为:

function BasePlugin:redirect()ngx.log(ngx.DEBUG, " executing plugin \"", self._name, "\": redirect")
end 

找一个例子分析一下,以RedirectHandler为例

function RedirectHandler:redirect()RedirectHandler.super.redirect(self)local enable = orange_db.get("redirect.enable")local meta = orange_db.get_json("redirect.meta")local selectors = orange_db.get_json("redirect.selectors")local ordered_selectors = meta and meta.selectorsif not enable or enable ~= true or not meta or not ordered_selectors or not selectors thenreturnendlocal ngx_var = ngx.varlocal ngx_var_uri = ngx_var.urilocal ngx_var_host = ngx_var.http_hostlocal ngx_var_scheme = ngx_var.schemelocal ngx_var_args = ngx_var.argsfor i, sid in ipairs(ordered_selectors) dongx.log(ngx.INFO, "==[Redirect][PASS THROUGH SELECTOR:", sid, "]")local selector = selectors[sid]if selector and selector.enable == true thenlocal selector_pass if selector.type == 0 then -- 全流量选择器selector_pass = trueelseselector_pass = judge_util.judge_selector(selector, "redirect")-- selector judgeendif selector_pass thenif selector.handle and selector.handle.log == true thenngx.log(ngx.INFO, "[Redirect][PASS-SELECTOR:", sid, "] ", ngx_var_uri)endlocal stop = filter_rules(sid, "redirect", ngx_var_uri, ngx_var_host, ngx_var_scheme, ngx_var_args)local selector_continue = selector.handle and selector.handle.continueif stop or not selector_continue then -- 不再执行此插件其他逻辑returnendelseif selector.handle and selector.handle.log == true thenngx.log(ngx.INFO, "[Redirect][NOT-PASS-SELECTOR:", sid, "] ", ngx_var_uri)endendendend
end 

可以发现,代码结构与rewrite非常相似。在来看看它的处理函数

local function filter_rules(sid, plugin, ngx_var_uri)local rules = orange_db.get_json(plugin .. ".selector." .. sid .. ".rules")if not rules or type(rules) ~= "table" or #rules <= 0 thenreturn falseendfor i, rule in ipairs(rules) doif rule.enable == true then-- judge阶段local pass = judge_util.judge_rule(rule, "rewrite")-- extract阶段local variables = extractor_util.extract_variables(rule.extractor)-- handle阶段if pass thenlocal handle = rule.handleif handle and handle.uri_tmpl thenlocal to_rewrite = handle_util.build_uri(rule.extractor.type, handle.uri_tmpl, variables)if to_rewrite and to_rewrite ~= ngx_var_uri thenif handle.log == true thenngx.log(ngx.INFO, "[Rewrite] ", ngx_var_uri, " to:", to_rewrite)endlocal from, to, err = ngx_re_find(to_rewrite, "[?]{1}", "jo")if not err and from and from >= 1 then--local qs = ngx_re_sub(to_rewrite, "[A-Z0-9a-z-_/]*[%?]{1}", "", "jo")local qs = string_sub(to_rewrite, from+1)to_rewrite = string_sub(to_rewrite, 1, from-1)if qs thenlocal args = ngx_decode_args(qs, 0)if args then ngx_set_uri_args(args) endendendngx_set_uri(to_rewrite, true)endendreturn trueendendendreturn false
end 

由此可见,实现这几个基本方法的大方法的写法几乎都是一致的,核心逻辑是放在filter_rules中的。 ​

所以,接下来应该分析这些核心方法中调用的handle_util、extractor_util、judge_util究竟干了些啥。

judge

让我们把视线放到utils包中,请看下面的思维导图

function _M.judge(condition)local condition_type = condition and condition.typeif not condition_type thenreturn falseendlocal operator = condition.operatorlocal expected = condition.valueif not operator or not expected thenreturn falseendlocal realif condition_type == "URI" thenreal = ngx.var.urielseif condition_type == "Query" thenlocal query = ngx.req.get_uri_args()real = query[condition.name]elseif condition_type == "Header" thenlocal headers = ngx.req.get_headers()real = headers[condition.name]elseif condition_type == "Cookie" thenlocal cookies = ngx.ctx.__cookies__if cookies thenreal = cookies:get(condition.name)endelseif condition_type == "IP" thenreal =ngx.var.remote_addrelseif condition_type == "Random" thenreal = ngx.now() * 1000 % 100elseif condition_type == "UserAgent" thenreal =ngx.var.http_user_agentelseif condition_type == "Method" thenlocal method = ngx.req.get_method()method = string_lower(method)if not expected or type(expected) ~= "string" thenexpected = ""endexpected = string_lower(expected)real = methodelseif condition_type == "PostParams" thenlocal headers = ngx.req.get_headers()local header = headers['Content-Type']if header thenlocal is_multipart = string_find(header, "multipart")if is_multipart and is_multipart > 0 thenreturn falseendendngx.req.read_body()local post_params, err = ngx.req.get_post_args()if not post_params or err thenngx.log(ngx.ERR, "[Condition Judge]failed to get post args: ", err)return falseendreal = post_params[condition.name]elseif condition_type == "Referer" thenreal =ngx.var.http_refererelseif condition_type == "Host" thenreal =ngx.var.hostendreturn assert_condition(real, operator, expected)
end 

这一大段根据输入进来的判断条件的类型来赋予待比较部分实际的值,根据代码可知,主要有

  • URI* 获取值的方法* real = ngx.var.uri
  • Query* 获取值的方法为* local query = ngx.req.get_uri_args()* real = query[condition.name]
  • Header* 取值* local headers = ngx.req.get_headers()* real = headers[condition.name]
  • Cookie* 取值* local cookies = ngx.ctx.cookies* real = cookies:get(condition.name)
  • IP* 取值* real = ngx.var.remote_addr
  • Random* 取值* real = ngx.now() * 1000 % 100
  • UserAgent* 取值* real = ngx.var.http_user_agent
  • Method* 取值* local method = ngx.req.get_method()* real = method
  • PostParams* 取值* ngx.req.read_body()* local post_params, err = ngx.req.get_post_args()* real = post_params[condition.name]
  • Referer* 取值* real = ngx.var.http_referer
  • Host* 取值* real = ngx.var.host

遗憾的是,这仅仅只是数据准备部分,真正核心的是assert_condition,还得再看看该函数的实现

local function assert_condition(real, operator, expected)if not real thenngx.log(ngx.ERR, string_format("assert_condition error: %s %s %s", real, operator, expected))return falseendif operator == 'match' thenif ngx_re_find(real, expected, 'isjo') ~= nil thenreturn trueendelseif operator == 'not_match' thenif ngx_re_find(real, expected, 'isjo') == nil thenreturn trueendelseif operator == "=" thenif real == expected thenreturn trueendelseif operator == "!=" thenif real ~= expected thenreturn trueendelseif operator == '>' thenif real ~= nil and expected ~= nil thenexpected = tonumber(expected)real = tonumber(real)if real and expected and real > expected thenreturn trueendendelseif operator == '>=' thenif real ~= nil and expected ~= nil thenexpected = tonumber(expected)real = tonumber(real)if real and expected and real >= expected thenreturn trueendendelseif operator == '<' thenif real ~= nil and expected ~= nil thenexpected = tonumber(expected)real = tonumber(real)if real and expected and real < expected thenreturn trueendendelseif operator == '<=' thenif real ~= nil and expected ~= nil thenexpected = tonumber(expected)real = tonumber(real)if real and expected and real <= expected thenreturn trueendendelseif operator == '%' thenlocal mod_num = 0;local value = {}local idx = 1--expected like: 50|1,2,3for i in re_gmatch(expected, "(\\d+)", "jsio") doif idx == 1 thenmod_num = tonumber(i[1])elsevalue[i[1]] = trueendidx = idx + 1endlocal mod_value = math.fmod(tonumber(real), mod_num)return value[tostring(mod_value)] == true;endreturn false
end 

看起来是一个统一处理两个值关系的方法,包括匹配,大于,小于,大于等于,小于等于和%的比较。 ​

看完了这些,思路就很清晰了,即首先从数据库中加载rule,如果judge为真,表明符合某个特征,然后就根据该rule做相应的处理。 ​

学习网络安全技术的方法无非三种:

第一种是报网络安全专业,现在叫网络空间安全专业,主要专业课程:程序设计、计算机组成原理原理、数据结构、操作系统原理、数据库系统、 计算机网络、人工智能、自然语言处理、社会计算、网络安全法律法规、网络安全、内容安全、数字取证、机器学习,多媒体技术,信息检索、舆情分析等。

第二种是自学,就是在网上找资源、找教程,或者是想办法认识一-些大佬,抱紧大腿,不过这种方法很耗时间,而且学习没有规划,可能很长一段时间感觉自己没有进步,容易劝退。

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

第三种就是去找培训。

image.png

接下来,我会教你零基础入门快速入门上手网络安全。

网络安全入门到底是先学编程还是先学计算机基础?这是一个争议比较大的问题,有的人会建议先学编程,而有的人会建议先学计算机基础,其实这都是要学的。而且这些对学习网络安全来说非常重要。但是对于完全零基础的人来说又或者急于转行的人来说,学习编程或者计算机基础对他们来说都有一定的难度,并且花费时间太长。

第一阶段:基础准备 4周~6周

这个阶段是所有准备进入安全行业必学的部分,俗话说:基础不劳,地动山摇
image.png

第二阶段:web渗透

学习基础 时间:1周 ~ 2周:

① 了解基本概念:(SQL注入、XSS、上传、CSRF、一句话木马、等)为之后的WEB渗透测试打下基础。
② 查看一些论坛的一些Web渗透,学一学案例的思路,每一个站点都不一样,所以思路是主要的。
③ 学会提问的艺术,如果遇到不懂得要善于提问。
image.png

配置渗透环境 时间:3周 ~ 4周:

① 了解渗透测试常用的工具,例如(AWVS、SQLMAP、NMAP、BURP、中国菜刀等)。
② 下载这些工具无后门版本并且安装到计算机上。
③ 了解这些工具的使用场景,懂得基本的使用,推荐在Google上查找。

渗透实战操作 时间:约6周:

① 在网上搜索渗透实战案例,深入了解SQL注入、文件上传、解析漏洞等在实战中的使用。
② 自己搭建漏洞环境测试,推荐DWVA,SQLi-labs,Upload-labs,bWAPP。
③ 懂得渗透测试的阶段,每一个阶段需要做那些动作:例如PTES渗透测试执行标准。
④ 深入研究手工SQL注入,寻找绕过waf的方法,制作自己的脚本。
⑤ 研究文件上传的原理,如何进行截断、双重后缀欺骗(IIS、PHP)、解析漏洞利用(IIS、Nignix、Apache)等,参照:上传攻击框架。
⑥ 了解XSS形成原理和种类,在DWVA中进行实践,使用一个含有XSS漏洞的cms,安装安全狗等进行测试。
⑦ 了解一句话木马,并尝试编写过狗一句话。
⑧ 研究在Windows和Linux下的提升权限,Google关键词:提权
image.png
以上就是入门阶段

第三阶段:进阶

已经入门并且找到工作之后又该怎么进阶?详情看下图
image.png

给新手小白的入门建议:
新手入门学习最好还是从视频入手进行学习,视频的浅显易懂相比起晦涩的文字而言更容易吸收,这里我给大家准备了一套网络安全从入门到精通的视频学习资料包免费领取哦!

如果你对网络安全入门感兴趣,那么你需要的话可以点击这里👉网络安全重磅福利:入门&进阶全套282G学习资源包免费分享!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值