├── series_10 ├── api_doc │ ├── .nojekyll │ ├── index.html │ ├── readme.md │ ├── node.md │ ├── mario.md │ ├── conf.md │ ├── metrics.md │ ├── account.md │ └── serializer.json ├── plugin_demo │ ├── 发现大量40X就封IP │ │ ├── README.md │ │ └── plugin.lua │ ├── 半小时内发现3次高危攻击就封IP │ │ ├── README.md │ │ └── plugin.lua │ ├── 发现暴力破解 │ │ ├── README.md │ │ └── brute.lua │ ├── 统计用户访问的域名 │ │ ├── README.md │ │ └── plugin.lua │ ├── 发现可能存在的后门 │ │ ├── README.md │ │ └── backdoor.lua │ ├── 统计每个域名的访问量 │ │ ├── README.md │ │ ├── plugin_counter.lua │ │ └── plugin_ticker.lua │ ├── ticker_hello_world │ │ ├── ticker_hello_world.png │ │ ├── README.md │ │ └── plugin.lua │ ├── process_hello_world │ │ ├── process_hello_world.png │ │ ├── README.md │ │ └── plugin.lua │ └── 提取真实外网IP │ │ ├── README.md │ │ └── detect_real_IP.lua └── plugin_sdk.md ├── series_20 ├── plugin_demo │ ├── 统计域名及其访问量 │ │ ├── README.md │ │ └── plugin.lua │ ├── 封禁特定地区|基于公开情报进行封禁 │ │ ├── README.md │ │ └── plugin.lua │ ├── 针对固定路径的Top统计 │ │ ├── README.md │ │ └── plugin.lua │ ├── 封禁频繁进行攻击的IP │ │ ├── README.md │ │ └── plugin.lua │ └── 耗时请求告警 │ │ ├── README.md │ │ └── plugin.lua ├── api_doc │ ├── index.html │ ├── readme.md │ ├── report.md │ ├── node.md │ ├── mario.md │ └── account.md └── plugin_sdk.md ├── README.md ├── LICENSE └── assets └── safeline_logo.svg /series_10/api_doc/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /series_10/plugin_demo/发现大量40X就封IP/README.md: -------------------------------------------------------------------------------- 1 | # 发现大量40X就封IP 2 | -------------------------------------------------------------------------------- /series_10/plugin_demo/半小时内发现3次高危攻击就封IP/README.md: -------------------------------------------------------------------------------- 1 | # 半小时内发现3次高危攻击就封IP 2 | 3 | 4 | -------------------------------------------------------------------------------- /series_10/plugin_demo/发现暴力破解/README.md: -------------------------------------------------------------------------------- 1 | # 发现暴力破解事件 2 | 3 | 分析是否存在某个 IP 短时间内频繁访问登录接口,分析结果会输出在插件日志里。 4 | -------------------------------------------------------------------------------- /series_10/plugin_demo/统计用户访问的域名/README.md: -------------------------------------------------------------------------------- 1 | # 半小时内发现3次高危攻击就封IP 2 | 3 | 访问新的域名以后会记录一条日志 4 | 5 | 6 | -------------------------------------------------------------------------------- /series_10/plugin_demo/发现可能存在的后门/README.md: -------------------------------------------------------------------------------- 1 | # 发现可能存在的后门 2 | 3 | 针对检测引擎发现的后门攻击,进一步分析是否返回了 200,如果返回 200 则认为可能是真实存在的后门,结果输出在插件日志中。 4 | -------------------------------------------------------------------------------- /series_10/plugin_demo/统计每个域名的访问量/README.md: -------------------------------------------------------------------------------- 1 | # 半小时内发现3次高危攻击就封IP 2 | 3 | 该功能需要两个插件配合 4 | 5 | plugin_counter 负责统计域名的访问量 6 | 7 | plugin_ticker 负责定时输出统计结果、定时清理垃圾数据 8 | 9 | 10 | -------------------------------------------------------------------------------- /series_10/plugin_demo/ticker_hello_world/ticker_hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/safeline-open-platform/HEAD/series_10/plugin_demo/ticker_hello_world/ticker_hello_world.png -------------------------------------------------------------------------------- /series_10/plugin_demo/process_hello_world/process_hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chaitin/safeline-open-platform/HEAD/series_10/plugin_demo/process_hello_world/process_hello_world.png -------------------------------------------------------------------------------- /series_10/plugin_demo/ticker_hello_world/README.md: -------------------------------------------------------------------------------- 1 | # Ticker Hello World 2 | 3 | SafeLine 每隔 10 秒会调用这个插件的 ticker 函数一次,并且通过 log 函数输出当前系统时间,运行效果如图。 4 | 5 | ![运行效果](./ticker_hello_world.png) 6 | -------------------------------------------------------------------------------- /series_10/plugin_demo/process_hello_world/README.md: -------------------------------------------------------------------------------- 1 | # Process Hello World 2 | 3 | 每个通过 SafeLine 的 HTTP 请求都会异步调用这个插件的 process 函数,并且通过 log 函数输出当前请求的路径,运行效果如图。 4 | 5 | ![运行效果](./process_hello_world.png) 6 | -------------------------------------------------------------------------------- /series_10/plugin_demo/ticker_hello_world/plugin.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local os = require "os" 3 | 4 | function ticker(dur) 5 | skynet.log("hello world", os.date("%H:%M:%S")) 6 | end 7 | 8 | skynet.register(skynet.TYPE_TICKER, 10, ticker) 9 | -------------------------------------------------------------------------------- /series_10/plugin_demo/提取真实外网IP/README.md: -------------------------------------------------------------------------------- 1 | 雷池Waf插件 2 | 3 | # 检测真实攻击者IP 4 | 5 | lua语言(5.1)编写; 6 | 7 | 适用于10、20版本,针对XXF多IP、多层CDN跳转、复杂网络,导致Waf无法识别源IP的情况,默认只检测高危行为的外网IP。在记录IP的同时也会记录攻击类型。 8 | 9 | ### 可添加白名单(在white_list中加入) 10 | 11 | 例如:`white = {'8.8.8.8','9.9.9.9'}` 12 | 13 | 如果白名单为空,则默认匹配所有的外网攻击者IP。 14 | 15 | --- 16 | 17 | 如有BUG可以提Issues,不定期修复; 18 | 19 | -------------------------------------------------------------------------------- /series_20/plugin_demo/统计域名及其访问量/README.md: -------------------------------------------------------------------------------- 1 | # 版本要求 2 | 3 | `>= 19.07.001` 4 | 5 | # 用途描述 6 | 7 | 统计 24 小时内所有被访问的域名及其访问量,数据每 1 分钟更新一次 8 | 9 | # 已知缺陷 10 | 11 | 插件需要对经过雷池的所有请求进行统计,CPU 开销和内存开销巨大,并且在未来没有大幅度改善的可能性,请仅在 QPS 较低的情况下使用 12 | 13 | # 性能参数 14 | 15 | - 单机版单核最大 QPS:20000 16 | - 内存消耗最大可达若干 GB,待进一步测试 17 | 18 | # 可调整参数 19 | 20 | - `arg_range` 整数,统计范围,单位为秒 21 | - `arg_period` 整数,统计周期,单位为秒 22 | 23 | 即:统计 `arg_range` 秒内所有被访问的域名及其访问量,数据每 `arg_period` 秒更新一次 24 | -------------------------------------------------------------------------------- /series_20/plugin_demo/封禁特定地区|基于公开情报进行封禁/README.md: -------------------------------------------------------------------------------- 1 | # 版本要求 2 | 3 | `>= 19.07.001` 4 | 5 | # 用途描述 6 | 7 | 基于公开的威胁情报(使用 `ipdata.co` 提供的情报源),封禁来自特定地区或已知威胁 ip 8 | 9 | # 适用场景 10 | 11 | 需要 waf 与某些威胁情报系统联动,本插件提供 POC 样例 12 | 13 | # 已知缺陷 14 | 15 | - 使用的情报提供方对 api 调用有限制,单个账号调用次数存在上限 16 | 17 | # 性能参数 18 | 19 | - 暂无测试 20 | 21 | # 可调整参数 22 | 23 | - `api_key` 字符串,`ipdata` 注册账号的私有 api key 24 | - `bantime` 整数,封禁时间,单位为秒 25 | - `blocks` 待封禁地区的 table,格式请参考插件内注释 26 | - `block_by_threat` 威胁情报项开关,参考插件内注释 27 | 28 | -------------------------------------------------------------------------------- /series_10/plugin_demo/发现可能存在的后门/backdoor.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local match = { 3 | host = ".*", 4 | urlpath = ".*", 5 | type = skynet.MATCH_TYPE_ALL, 6 | target = skynet.MATCH_TARGET_DETECT, 7 | } 8 | 9 | function process() 10 | local info = skynet.get_detailed_info() 11 | if info.attack_type == 5 and info.resp_status_code == "200" then 12 | skynet.log('backdoor', string.format("发现疑似后门: [%s%s], 请求 IP 为 [%s]", info.host, info.urlpath, info.src_ip)) 13 | end 14 | end 15 | 16 | skynet.register(skynet.TYPE_PROCESS, match, process) -------------------------------------------------------------------------------- /series_20/plugin_demo/针对固定路径的Top统计/README.md: -------------------------------------------------------------------------------- 1 | # 版本要求 2 | 3 | `>= 19.07.001` 4 | 5 | # 用途描述 6 | 7 | 统计 10 分钟内访问某一路径最频繁的前 10 个 IP,数据每 5 秒更新一次 8 | 9 | # 适用场景 10 | 11 | 帮助用户了解特点页面/API 的实时访问情况,相比 「网站统计 → 实时访客统计」页面有更强的可定制性 12 | 13 | # 性能参数 14 | 15 | - 单机版单核最大 QPS:20000 16 | 17 | # 可调整参数 18 | 19 | - `arg_host` 统计的目标域名 20 | - `arg_url_path` 统计的目标路径 21 | - `arg_range` 整数,统计范围,单位为秒 22 | - `arg_period` 整数,统计周期(多久统计一次),单位为秒 23 | - `arg_threshold` 整数,统计最访问频繁前 N 个 IP 24 | 25 | 即:统计 `arg_range` 秒内访问 `arg_host` / `arg_url_path` 最频繁的前 `arg_threshold` 个 IP,数据每 `arg_period` 秒更新一次 26 | -------------------------------------------------------------------------------- /series_10/plugin_demo/process_hello_world/plugin.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local match = { 4 | ip = "0.0.0.0/0", -- 字符串类型,要匹配的 ip 段,使用 cidr 记法 5 | host = ".*\\.chaitin\\.cn", -- 字符串类型,要匹配的域名,支持 perl 风格的正则表达式 6 | urlpath = ".*", -- 字符串类型,访问路径,同样支持正则 7 | type = skynet.MATCH_TYPE_ALL, 8 | target = skynet.MATCH_TARGET_ALL, 9 | } 10 | 11 | 12 | function process(ip, host, urlpath) 13 | skynet.log("hello world", host .. urlpath) 14 | end 15 | 16 | skynet.register(skynet.TYPE_PROCESS, match, process) 17 | -------------------------------------------------------------------------------- /series_10/plugin_demo/统计用户访问的域名/plugin.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | match = { 4 | ip = "0.0.0.0/0", 5 | -- host = ".+", 6 | -- urlpath = ".+", 7 | type = skynet.MATCH_TYPE_ALL, 8 | target = skynet.MATCH_TARGET_ALL, 9 | } 10 | 11 | function process(ip, host, urlpath) 12 | if skynet.db_get(skynet.DB_LOCAL, host) == nil then 13 | skynet.db_set(skynet.DB_LOCAL, host, host) 14 | skynet.log("new-ip", host) 15 | -- or skynet.http_post(...) 16 | end 17 | end 18 | 19 | skynet.register(skynet.TYPE_PROCESS, match, process) 20 | -------------------------------------------------------------------------------- /series_20/plugin_demo/封禁频繁进行攻击的IP/README.md: -------------------------------------------------------------------------------- 1 | # 版本要求 2 | 3 | `>= 19.07.001` 4 | 5 | # 用途描述 6 | 7 | 封禁在 30 分钟内发起 3 次或以上攻击的 IP,生效周期为 5 秒 8 | 9 | # 适用场景 10 | 11 | 客户业务敏感,拒绝为有攻击行为的 IP 提供服务时可以使用此插件 12 | 13 | # 已知缺陷 14 | 15 | - 目前 Plumber SQL 不支持以攻击检测日志作为数据源,从访问日志中筛选攻击请求导致了一些额外开销 16 | - 当前的实现会重复封禁 IP,对管理后台压力较大 17 | 18 | # 性能参数 19 | 20 | - 单机版单核最大 QPS:15000 21 | - 每封禁一个 IP 带来的单次执行开销约为 5ms 22 | 23 | # 可调整参数 24 | 25 | - `arg_range` 整数,统计范围,单位为秒 26 | - `arg_period` 整数,统计周期(多久统计一次),单位为秒 27 | - `arg_threshold` 整数,封禁阈值,当攻击数超过该值时执行封禁操作 28 | 29 | 即:封禁在 `arg_range` 秒内发起 `arg_threshold` 次或以上攻击的 IP,生效周期为 `arg_period` 秒 30 | -------------------------------------------------------------------------------- /series_20/plugin_demo/耗时请求告警/README.md: -------------------------------------------------------------------------------- 1 | # 版本要求 2 | 3 | `>= 19.07.001` 4 | 5 | # 用途描述 6 | 7 | 筛选出请求中耗时超过 0.5 秒的请求,每隔 5 秒以插件日志的形式告警,内容包括:报告用户 IP、目标站点,目标路径以及平均耗时。5 秒内重复的请求(IP,目标站点,目标路径相同视为重复)只汇报一次。 8 | 9 | # 适用场景 10 | 11 | 通常耗时的请求会给服务器带来更大的压力,本插件用于帮助客户筛选出对网站造成压力的请求,帮助客户排查问题(恶意请求 or 正常现象) 12 | 13 | # 已知缺陷 14 | 15 | - 暂不支持 AVG 函数,因此暂时使用 `SUM(time)/COUNT(ip)` 作为替代 16 | 17 | # 性能参数 18 | 19 | - 单机版单核最大 QPS:20000 20 | 21 | # 可调整参数 22 | 23 | - `arg_threshold` 浮点数,请求耗时阈值,耗时超过该值的请求会被记录,单位为秒 24 | - `arg_period` 整数,统计周期,统一周期内地重复请求只回报一次,单位为秒 25 | 26 | 即:筛选出请求中耗时超过 `arg_threshold` 秒的请求,每隔 `arg_period` 秒以插件日志的形式告警,`arg_period` 秒内重复的请求(IP,目标站点,目标路径相同视为重复)只汇报一次。 27 | -------------------------------------------------------------------------------- /series_10/api_doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Document 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /series_20/plugin_demo/耗时请求告警/plugin.lua: -------------------------------------------------------------------------------- 1 | local safeline = require("safeline") 2 | 3 | local arg_threshold = 1.0 4 | local arg_period = 5 5 | 6 | local query = string.format([[ 7 | SELECT ip, host, url_path, SUM(time)/COUNT(ip) AS avg_time 8 | FROM access_log WHERE time > %f 9 | GROUP BY TUMBLE_WINDOW(NOW(), %d), ip, host, url_path 10 | ]], arg_threshold, arg_period) 11 | 12 | function process(key, rows) 13 | local ip_list = "" 14 | for row in rows do 15 | ip_list = ip_list .. string.format("IP %s 访问 %s/%s 耗时 %f 秒,告警阈值为 %f 秒\n", row.ip, row.host, row.url_path, row.avg_time, arg_threshold) 16 | end 17 | if ip_list ~= "" then 18 | safeline.log("警告", ip_list) 19 | end 20 | end 21 | 22 | safeline.register(safeline.TYPE_QUERY, query, process) 23 | -------------------------------------------------------------------------------- /series_20/plugin_demo/统计域名及其访问量/plugin.lua: -------------------------------------------------------------------------------- 1 | local safeline = require("safeline") 2 | 3 | local arg_range = 60 * 60 * 24 4 | local arg_period = 60 5 | 6 | local query = string.format([[ 7 | SELECT host, COUNT(host) AS count_ 8 | FROM access_log 9 | GROUP BY SLIDE_WINDOW(NOW(), %d, %d), host 10 | ORDER BY COUNT(host) 11 | ]], arg_range, arg_period) 12 | 13 | function process(key, rows) 14 | local domain_list = "" 15 | for row in rows do 16 | domain_list = domain_list .. string.format("域名 %s 在最近 %d 小时内被访问了 %d 次\n", row.host, arg_range / (60 * 60), row.count_) 17 | end 18 | if domain_list == "" then 19 | safeline.log("统计", "暂无已知的域名") 20 | else 21 | safeline.log("统计", domain_list) 22 | end 23 | end 24 | 25 | safeline.register(safeline.TYPE_QUERY, query, process) 26 | -------------------------------------------------------------------------------- /series_10/plugin_demo/发现大量40X就封IP/plugin.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | local limit = 50 -- 产生4xx错误的最大次数 3 | local duration = 120 -- 过去几分钟内(两分钟) 4 | local bantime = 2 * 24 * 3600 -- 封禁的秒数 5 | 6 | -- 匹配规则是对所有IP进行匹配 7 | 8 | local match = { 9 | host = ".*", 10 | urlpath = ".*", 11 | type = skynet.MATCH_TYPE_DEFAULT, 12 | target = skynet.MATCH_TARGET_ALL 13 | } 14 | 15 | skynet.register(skynet.TYPE_PROCESS, match, 16 | function(ip, host, urlpath) 17 | -- 将会统计所有的IP的在过去两分钟内的次数。 18 | local ipkey = {ip = ip} 19 | local stat = skynet.stat_resp_code(ipkey, duration) 20 | 21 | -- 封大于50次的人两天 22 | local st4xx = stat[skynet.RESP_CODE_4XX] 23 | if st4xx ~= nil and st4xx > limit then 24 | skynet.action_ban(ipkey, bantime) 25 | end 26 | end 27 | ) 28 | -------------------------------------------------------------------------------- /series_20/api_doc/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Skyview Document 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /series_10/plugin_demo/统计每个域名的访问量/plugin_counter.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local match = { 4 | ip = "0.0.0.0/0", 5 | type = skynet.MATCH_TYPE_ALL, 6 | target = skynet.MATCH_TARGET_ACCESS, 7 | } 8 | 9 | local key_prefix = "domain" 10 | local index = key_prefix .. ":" .. "index" 11 | 12 | function process(ip, host, urlpath) 13 | local key = key_prefix .. ":" .. host 14 | 15 | -- Add host to domain index 16 | if tonumber(skynet.db_get(skynet.DB_GLOBAL, key)) == nil then 17 | domains = skynet.db_get(skynet.DB_GLOBAL, index) 18 | if domains == nil then 19 | skynet.db_set(skynet.DB_GLOBAL, index, host) 20 | else 21 | skynet.db_set(skynet.DB_GLOBAL, index, domains .. " " .. host) 22 | end 23 | end 24 | 25 | skynet.db_add(skynet.DB_GLOBAL, key, 1) 26 | end 27 | 28 | skynet.register(skynet.TYPE_PROCESS, match, process) 29 | -------------------------------------------------------------------------------- /series_20/plugin_demo/封禁频繁进行攻击的IP/plugin.lua: -------------------------------------------------------------------------------- 1 | local safeline = require("safeline") 2 | 3 | local arg_range = 60 * 30 4 | local arg_period = 5 5 | local arg_threshold = 3 6 | 7 | local query = string.format([[ 8 | SELECT ip 9 | FROM access_log 10 | WHERE req_block_reason = "web" 11 | GROUP BY SLIDE_WINDOW(NOW(), %d, %d), ip 12 | HAVING COUNT(ip) >= %d 13 | ]], arg_range, arg_period, arg_threshold) 14 | 15 | function process(key, rows) 16 | local total_num = 0 17 | local err_num = 0 18 | for row in rows do 19 | err = safeline.action_ban(safeline.ACTION_SCOPE_ALL, { ip = row.ip }, 3600) 20 | total_num = total_num + 1 21 | if err ~= nil then 22 | err_num = err_num + 1 23 | end 24 | end 25 | if total_num ~= 0 then 26 | safeline.log("信息", string.format("本次需要封禁 %d 个 IP,其中 %d 个封禁失败", total_num, err_num)) 27 | end 28 | end 29 | 30 | safeline.register(safeline.TYPE_QUERY, query, process) 31 | -------------------------------------------------------------------------------- /series_20/plugin_demo/针对固定路径的Top统计/plugin.lua: -------------------------------------------------------------------------------- 1 | local safeline = require("safeline") 2 | 3 | local arg_host = "chaitin.cn" 4 | local arg_url_path = "/safeline.html" 5 | local arg_range = 60 * 10 6 | local arg_period = 5 7 | local arg_threshold = 10 8 | 9 | local query = string.format([[ 10 | SELECT ip, COUNT(ip) AS count_ 11 | FROM access_log 12 | WHERE (host = %q) AND (url_path = %q) 13 | GROUP BY SLIDE_WINDOW(NOW(), %d, %d), ip 14 | ORDER BY COUNT(host) 15 | LIMIT %d 16 | ]], arg_host, arg_url_path, arg_range, arg_period, arg_threshold) 17 | 18 | function process(key, rows) 19 | local top_list = "" 20 | local i = 0 21 | for row in rows do 22 | top_list = top_list .. string.format("#%d. IP %s 在最近 %d 分钟内访问了 %s%s %d 次\n", i, row.ip, arg_range/60, arg_host, arg_url_path, row.count_) 23 | i = i + 1 24 | end 25 | if top_list == "" then 26 | safeline.log("统计", string.format("最近 %d 分钟内没有任何访问 %s%s 的请求", arg_range/60, arg_host, arg_url_path)) 27 | else 28 | safeline.log("统计", top_list) 29 | end 30 | end 31 | 32 | safeline.register(safeline.TYPE_QUERY, query, process) 33 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 长亭 SafeLine 开放平台 2 | 3 | ## SafeLine(雷池) 4 | 5 | 6 | logo 7 | 8 | 9 | 10 | SafeLine(雷池)是由长亭科技推出的一款以智能语义分析算法著称的 Web 攻击防护产品,在大幅降低了漏报率和误报率的同时还能保持极高的检测效率,可以为用户提供高质量的 Web 攻击防护、CC 攻击防护、访问控制、防护统计等功能。 11 | 12 | 13 | [长亭科技](https://www.chaitin.cn/) | [长亭 SafeLine](https://www.chaitin.cn/zh/safeline) 14 | 15 | 16 | 17 | ## SafeLine Open API 18 | 19 | 20 | 21 | ## SafeLine 插件平台 22 | 23 | 作为 Web 流量处理网关,SafeLine 离线流量分析引擎提供了丰富的扩展接口,支持以更多自定义的方式来处理收到的 HTTP 请求,使用者可以通过自主编写 SafeLine 插件的方式完成用户的流量分析逻辑。 24 | 25 | 多数插件平台的使用者会自主编写插件来输出场景化的 Web 访问统计与 Web 攻击统计,或者编写插件与其他安全产品及风控系统整和对攻击者进行多维度深度检测与阻断。 26 | 27 | SafeLine 插件平台可以解决的问题包括但不限于复杂 CC 攻击、薅羊毛、恶意刷注册、恶意刷评论、暴力破解、脱裤、以及绝大部分用户遇到的业务安全问题。 28 | 29 | SafeLine 插件平台 SDK 文档见 30 | [10 系列](./series_10/plugin_sdk.md) | [20 系列](./series_20/plugin_sdk.md) 31 | 32 | SafeLine 插件 Demo 见 33 | [10 系列](./series_10/plugin_demo/) | [20 系列(可参考 10 系列)](./series_20/plugin_demo/) 34 | 35 | 36 | ## License 37 | 38 | [MIT](./LICENSE) 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [year] [fullname] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /series_10/api_doc/readme.md: -------------------------------------------------------------------------------- 1 | ## SkyView 文档 2 | 3 | ```js 4 | // _oo0oo_ 5 | // o8888888o 6 | // 88" . "88 7 | // (| -_- |) 8 | // 0\ = /0 9 | // ___/`---'\___ 10 | // .' \\| |// '. 11 | // / \\||| : |||// \ 12 | // / _||||| -:- |||||- \ 13 | // | | \\\ - /// | | 14 | // | \_| ''\---/'' |_/ | 15 | // \ .-\__ '-' ___/-. / 16 | // ___'. .' /--.--\ `. .'___ 17 | // ."" '< `.___\_<|>_/___.' >' "". 18 | // | | : `- \`.;`\ _ /`;.`/ - ` : | | 19 | // \ \ `_. \_ __\ /__ _/ .-` / / 20 | // =====`-.____`.___ \_____/___.-`___.-'===== 21 | // `=---=' 22 | // 23 | // 24 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 25 | // 26 | // 佛祖保佑 永无BUG 27 | ``` 28 | 29 | 模块列表 30 | 31 | - [mario](mario.md) 32 | - [conf2](conf2.md) 33 | - [acl](acl.md) 34 | - [metrics](metrics.md) 35 | - [conf](conf.md) 36 | - [account](account.md) 37 | - [node](node.md) 38 | - [management](management.md) 39 | 40 | 41 | 最后更新时间: `2018-12-20 09:17:53.100041` -------------------------------------------------------------------------------- /series_10/plugin_demo/发现暴力破解/brute.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local match = { 4 | host = ".*", 5 | urlpath = ".*", 6 | type = skynet.MATCH_TYPE_DEFAULT, 7 | target = skynet.MATCH_TARGET_ACCESS 8 | } 9 | 10 | function check_urlpath(urlpath) 11 | urlpath_l = string.lower(urlpath) 12 | kws = {"login"} 13 | for i, s in ipairs(kws) do 14 | if string.find(urlpath_l, s, 0) ~= nil then 15 | return true 16 | end 17 | end 18 | return false 19 | end 20 | 21 | function cat(host, urlpath) 22 | if string.sub(host, -1) == "/" and string.sub(urlpath, 0, 1) == '/' then 23 | return string.sub(host, 0, -2)..urlpath 24 | elseif string.sub(host, -1) ~= "/" and string.sub(urlpath, 0, 1) ~= '/' then 25 | return host.."/"..urlpath 26 | else 27 | return host..urlpath 28 | end 29 | end 30 | 31 | function process(ip, host, urlpath) 32 | ip_domain_paths = skynet.top_n(skynet.TOP_IP_HOST_URLPATH, 100, 5) 33 | for i, record in pairs(ip_domain_paths) do 34 | if record["count"] >= 10 then 35 | if check_urlpath(record["urlpath"]) then 36 | skynet.log("[brute]", string.format("发现 IP [%s] 疑似在尝试对 [%s] 进行暴力破解攻击", record["ip"], cat(record["host"], record["urlpath"]))) 37 | end 38 | end 39 | end 40 | end 41 | 42 | skynet.register(skynet.TYPE_PROCESS, match, process) 43 | -------------------------------------------------------------------------------- /series_10/plugin_demo/统计每个域名的访问量/plugin_ticker.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | local duration = 60 * 60 * 24 4 | 5 | local key_prefix = "domain" 6 | local index = key_prefix .. ":" .. "index" 7 | 8 | function split(str) 9 | fields = {} 10 | for token in string.gmatch(str, "[^%s]+") do 11 | table.insert(fields, token) 12 | end 13 | return fields 14 | end 15 | 16 | function ticker(dur) 17 | local result = "" 18 | local domains = skynet.db_get(skynet.DB_GLOBAL, index) 19 | local map = {} 20 | 21 | if domains ~= nil then 22 | -- Domains deduplicate 23 | domains = split(domains) 24 | for i, domain in ipairs(domains) do 25 | map[domain] = true 26 | end 27 | end 28 | 29 | for domain, _ in pairs(map) do 30 | local key = key_prefix .. ":" .. domain 31 | local val = tonumber(skynet.db_get(skynet.DB_GLOBAL, key)) 32 | if val ~= nil then 33 | if result == "" then 34 | result = domain .. " " .. val 35 | else 36 | result = result .. ", " .. domain .. " " .. val 37 | end 38 | end 39 | end 40 | 41 | skynet.db_del(skynet.DB_GLOBAL, index) 42 | for domain, _ in pairs(map) do 43 | local key = key_prefix .. ":" .. domain 44 | skynet.db_del(skynet.DB_GLOBAL, key) 45 | end 46 | 47 | if result == "" then 48 | result = "" 49 | end 50 | skynet.log("domain-ticker", result) 51 | end 52 | 53 | skynet.register(skynet.TYPE_TICKER, duration, ticker) 54 | -------------------------------------------------------------------------------- /series_20/api_doc/readme.md: -------------------------------------------------------------------------------- 1 | ## Skyview 文档 2 | 3 | ```js 4 | _ _ 5 | | | (_) 6 | ___ | | __ _ _ __ __ _ ___ __ __ 7 | / __| | |/ / | | | | \ \ / / | | / _ \ \ \ /\ / / 8 | \__ \ | < | |_| | \ V / | | | __/ \ V V / 9 | |___/ |_|\_\ \__, | \_/ |_| \___| \_/\_/ 10 | __/ | 11 | |___/ 12 | 13 | 14 | // _oo0oo_ 15 | // o8888888o 16 | // 88" . "88 17 | // (| -_- |) 18 | // 0\ = /0 19 | // ___/`---'\___ 20 | // .' \\| |// '. 21 | // / \\||| : |||// \ 22 | // / _||||| -:- |||||- \ 23 | // | | \\\ - /// | | 24 | // | \_| ''\---/'' |_/ | 25 | // \ .-\__ '-' ___/-. / 26 | // ___'. .' /--.--\ `. .'___ 27 | // ."" '< `.___\_<|>_/___.' >' "". 28 | // | | : `- \`.;`\ _ /`;.`/ - ` : | | 29 | // \ \ `_. \_ __\ /__ _/ .-` / / 30 | // =====`-.____`.___ \_____/___.-`___.-'===== 31 | // `=---=' 32 | // 33 | // 34 | // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 35 | // 36 | // 佛祖保佑 永无BUG 37 | ``` 38 | 39 | 模块列表 40 | 41 | - [options](options.md) 42 | - [node](node.md) 43 | - [account](account.md) 44 | - [mario](mario.md) 45 | - [report](report.md) 46 | - [website](website.md) 47 | - [policy](policy.md) 48 | - [acl](acl.md) 49 | - [management](management.md) 50 | 51 | 52 | 最后更新时间: `2018-12-11 07:32:44.670974` -------------------------------------------------------------------------------- /series_10/plugin_demo/半小时内发现3次高危攻击就封IP/plugin.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | match = { 4 | ip = "0.0.0.0/0", 5 | host = ".+", 6 | urlpath = ".+", 7 | type = skynet.MATCH_TYPE_ALL, 8 | target = skynet.MATCH_TARGET_DETECT, 9 | } 10 | 11 | function clear(ip) 12 | skynet.db_del(skynet.DB_LOCAL, ip..":ts") 13 | skynet.db_del(skynet.DB_LOCAL, ip..":attack") 14 | end 15 | 16 | function reset(log, ip) 17 | skynet.db_set(skynet.DB_LOCAL, ip..":ts", tostring(log.timestamp)) 18 | skynet.db_add(skynet.DB_LOCAL, ip..":attack", 1) 19 | end 20 | 21 | function process(ip, host, urlpath) 22 | local attack_limit = 3 -- 累积攻击次数 23 | local check_dur = 60 * 30 -- 攻击次数统计时间,单位秒 24 | local ban_dur = 60 * 60 -- 封禁时间,单位秒 25 | local ban_risk = 3 -- 需要统计的威胁等级,3 代表只统计高危,2 代表统计中高危,1代表统计中高低危 26 | 27 | local log = skynet.get_detailed_info() 28 | 29 | if log.risk_level < ban_risk then 30 | return 31 | end 32 | 33 | local ts = skynet.db_get(skynet.DB_LOCAL, ip..":ts") 34 | 35 | if ts == nil then 36 | -- first time record 37 | reset(log, ip) 38 | else 39 | if log.timestamp - tonumber(ts) < check_dur then 40 | local attack = skynet.db_get(skynet.DB_LOCAL, ip..":attack") 41 | if attack ~= nil and tonumber(attack) >= attack_limit then 42 | -- exceed attack limit 43 | local key = {ip = ip} 44 | skynet.action_ban(key, ban_dur) 45 | skynet.log("process", "ban "..ip) 46 | clear(ip) 47 | else 48 | -- normal record 49 | skynet.db_add(skynet.DB_LOCAL, ip..":attack", 1) 50 | end 51 | else 52 | -- exceed check duration, reset record 53 | clear(ip) 54 | reset(log, ip) 55 | end 56 | end 57 | end 58 | 59 | skynet.register(skynet.TYPE_PROCESS, match, process) 60 | -------------------------------------------------------------------------------- /series_10/plugin_demo/提取真实外网IP/detect_real_IP.lua: -------------------------------------------------------------------------------- 1 | local skynet = require "skynet" 2 | 3 | match = { 4 | ip = "0.0.0.0/0", 5 | host = ".+", 6 | urlpath = ".+", 7 | type = skynet.MATCH_TYPE_ALL, 8 | target = skynet.MATCH_TARGET_DETECT, 9 | } 10 | 11 | function legalIp(ip) 12 | 13 | white = {'8.8.8.8','9.9.9.9'} 14 | c = ip 15 | ip_part = {} 16 | flag = 0 17 | for i,value in pairs(white) do 18 | if value==ip then 19 | return nil 20 | end 21 | end 22 | 23 | for i in string.gmatch(c,"%d+") do 24 | if tonumber(i)>255 or tonumber(i)<0 then 25 | return nil 26 | end 27 | ip_part[#ip_part+1]=i 28 | end 29 | if ip_part[1]=='10' or ip_part[1]=='127' then 30 | return nil 31 | elseif ip_part[1]=='172' and (tonumber(ip_part[2])>=16 and tonumber(ip_part[2])<=31) then 32 | return nil 33 | elseif ip_part[1]=='192' and ip_part[2]=='168' then 34 | return nil 35 | end 36 | if flag ==0 then 37 | return 1 38 | end 39 | end 40 | 41 | 42 | function getIp(content) 43 | local ipArray = "" 44 | for c in string.gmatch(content,"%d+%.%d+%.%d+%.%d+") do 45 | if c ~= nil and legalIp(c) then 46 | if string.find(ipArray,c) == nil then 47 | ipArray = ipArray ..c..", " 48 | content = string.gsub(content,c,'') 49 | end 50 | end 51 | end 52 | return ipArray 53 | end 54 | 55 | 56 | function process(ip, host, urlpath) 57 | local ban_risk = 3 58 | 59 | local log = skynet.get_detailed_info() 60 | local attack_num = log.attack_type+1 61 | if log.risk_level < ban_risk then 62 | return 63 | end 64 | local ans = getIp(log.req_header_raw) 65 | att_type ={'sql_injection', 'xss', 'csrf', 'ssrf', 'dos', 'backdoor', 'deserialization', 'code_execution', 'code_injection', 'command_injection', 'file_upload', 'file_inclusion', 'redirect', 'weak_permission', 'info_leak', 'unauthorized_access', 'unsafe_config', 'xxe', 'xpath_injection', 'ldap_injection', 'directory_traversal', 'scanner', 'permission_bypass', 'acl_bypass', 'file_write', 'file_download', 'file_deletion', 'logic_error', 'crlf_injection', 'ssti', 'click_hijacking', 'buffer_overflow', 'integer_overflow', 'format_string', 'race_condition', 'timeout', 'unknown'} 66 | 67 | if ans == "" then 68 | ans = log.src_ip 69 | skynet.log(att_type[attack_num],ans) 70 | else 71 | new_ans = string.gsub(ans,log.dest_ip,'') 72 | skynet.log(att_type[attack_num],string.sub(new_ans,1,-3)) 73 | end 74 | end 75 | 76 | skynet.register(skynet.TYPE_PROCESS, match, process) -------------------------------------------------------------------------------- /series_20/api_doc/report.md: -------------------------------------------------------------------------------- 1 | # /api/ReportSubscriptionAPI 2 | 3 | ## GET 4 | 5 | 6 | 7 | ### - 获取报告订阅列表 8 | 9 | 10 | 11 | ?> 本 API 支持翻页参数 12 | 13 | 响应 14 | 15 | ```js 16 | { 17 | "err": null, 18 | "data": [ 19 | { 20 | "id": 5, 21 | "name": "foobar", 22 | "type": "day", 23 | "targets": [ 24 | "https://foobar.com" 25 | ], 26 | "create_time": "1544513554" 27 | } 28 | ], 29 | "msg": null 30 | } 31 | ``` 32 | 33 | ## POST 34 | 35 | ### [数据格式定义] 36 | 数据格式 37 | 38 | |字段名|数据类型|是否必填/默认值|NULL|其他| 39 | |:--|:-:|:-:|:-:|:-:| 40 | |name | 字符串 | 必填 | False | - | 41 | |targets | 列表 | 非必填/ | False | [ - 详见下方表格](#uodxcanjdyrszdnudcuhkyhwgujttqtq) | 42 | |type | 指定选项 | 必填 | False | 选项是: ['day', 'week', 'month'] | 43 | 44 | targets 45 | 46 | |字段名|数据类型|是否必填/默认值|NULL|其他| 47 | |:--|:-:|:-:|:-:|:-:| 48 | |子字段 | 字符串 | 必填 | False | URL; | 49 | 50 | 51 | 52 | ### - 创建一个报告订阅 53 | 54 | 55 | 56 | 请求 57 | 58 | ```js 59 | { 60 | "name": "foobar", 61 | "targets": [ 62 | "https://foobar.com" 63 | ], 64 | "type": "day" 65 | } 66 | ``` 67 | 68 | 响应 69 | 70 | ```js 71 | { 72 | "err": null, 73 | "data": { 74 | "id": 3, 75 | "name": "foobar", 76 | "type": "day", 77 | "targets": [ 78 | "https://foobar.com" 79 | ], 80 | "create_time": "1544513553" 81 | }, 82 | "msg": null 83 | } 84 | ``` 85 | 86 | ## DELETE 87 | 88 | ### [数据格式定义] 89 | 数据格式 90 | 91 | |字段名|数据类型|是否必填/默认值|NULL|其他| 92 | |:--|:-:|:-:|:-:|:-:| 93 | |id__in | 列表 | 非必填/ | False | [ - 详见下方表格](#dvbcmnyztjozvkybrqukdeptuhwtypjm) | 94 | 95 | id__in 96 | 97 | |字段名|数据类型|是否必填/默认值|NULL|其他| 98 | |:--|:-:|:-:|:-:|:-:| 99 | |子字段 | 整型数字 | 必填 | False | - | 100 | 101 | 102 | 103 | ### - 删除一个报告订阅 104 | 105 | 106 | 107 | 请求 108 | 109 | ```js 110 | { 111 | "id": [ 112 | 4 113 | ] 114 | } 115 | ``` 116 | 117 | 响应 118 | 119 | ```js 120 | { 121 | "err": null, 122 | "data": null, 123 | "msg": null 124 | } 125 | ``` 126 | 127 | # /api/ManualGenerateReportAPI 128 | 129 | ## POST 130 | 131 | ### [数据格式定义] 132 | 数据格式 133 | 134 | |字段名|数据类型|是否必填/默认值|NULL|其他| 135 | |:--|:-:|:-:|:-:|:-:| 136 | |time_range | 字符串 | 必填 | False | 时间范围,比如 1234-4567, 1234-0; | 137 | |name | 字符串 | 必填 | False | - | 138 | |targets | 列表 | 非必填/ | False | [ - 详见下方表格](#iewakbfkzsrnavgjkymvtcrdmejtuiow) | 139 | |sync | 布尔 | 非必填/False | False | - | 140 | |type | 指定选项 | 必填 | False | 选项是: ['day', 'week', 'custom', 'month'] | 141 | 142 | targets 143 | 144 | |字段名|数据类型|是否必填/默认值|NULL|其他| 145 | |:--|:-:|:-:|:-:|:-:| 146 | |子字段 | 字符串 | 必填 | False | URL; | 147 | 148 | 149 | 150 | ### - 手动生成一个报告 151 | 152 | 153 | 154 | 请求 155 | 156 | ```js 157 | { 158 | "name": "foobar", 159 | "targets": [], 160 | "time_range": "1541001600-1541087999", 161 | "sync": true, 162 | "type": "day" 163 | } 164 | ``` 165 | 166 | 响应 167 | 168 | ```js 169 | { 170 | "err": null, 171 | "data": { 172 | "id": 1, 173 | "name": "foobar", 174 | "type": "day", 175 | "targets": [], 176 | "create_time": "1544513552" 177 | }, 178 | "msg": null 179 | } 180 | ``` 181 | 182 | # /api/ReportResultAPI 183 | 184 | ## GET 185 | 186 | ### [数据格式定义] 187 | 数据格式 188 | 189 | |字段名|数据类型|是否必填/默认值|NULL|其他| 190 | |:--|:-:|:-:|:-:|:-:| 191 | |type | 指定选项 | 非必填/无默认值 | False | 选项是: ['day', 'week', 'custom', 'month'] | 192 | |targets | 列表 | 非必填/ | False | [ - 详见下方表格](#nnjecfyyqsuiwazlidtiomsldwonwzxc) | 193 | |name__like | 列表 | 非必填/ | False | [ - 详见下方表格](#ankoyivlfqjzxvnexohrxykydddgtpxu) | 194 | |timestamp__range | 列表 | 必填 | False | [ - 详见下方表格](#seixqrvenscoqlyuazzxocpcbnppqadc) | 195 | 196 | targets 197 | 198 | |字段名|数据类型|是否必填/默认值|NULL|其他| 199 | |:--|:-:|:-:|:-:|:-:| 200 | |子字段 | 字符串 | 必填 | False | - | 201 | 202 | 203 | name__like 204 | 205 | |字段名|数据类型|是否必填/默认值|NULL|其他| 206 | |:--|:-:|:-:|:-:|:-:| 207 | |子字段 | 字符串 | 必填 | False | - | 208 | 209 | 210 | timestamp__range 211 | 212 | |字段名|数据类型|是否必填/默认值|NULL|其他| 213 | |:--|:-:|:-:|:-:|:-:| 214 | |子字段 | 字符串 | 必填 | False | 时间范围,比如 1234-4567, 1234-0; | 215 | 216 | 217 | 218 | ### - 获取报告列表 219 | 220 | 221 | 222 | ?> 本 API 支持翻页参数 223 | 224 | 请求 225 | 226 | ```js 227 | { 228 | "type": [ 229 | "custom", 230 | "day" 231 | ], 232 | "name__like": [ 233 | "f" 234 | ] 235 | } 236 | ``` 237 | 238 | 响应 239 | 240 | ```js 241 | { 242 | "err": null, 243 | "data": [ 244 | { 245 | "id": 3, 246 | "time_range": "1531971605-1531981729", 247 | "state": "Pending", 248 | "create_time": "1544513553", 249 | "message_id": "", 250 | "end_time": null, 251 | "name": "foobar", 252 | "type": "day", 253 | "targets": [] 254 | } 255 | ], 256 | "msg": null 257 | } 258 | ``` 259 | 260 | ## DELETE 261 | 262 | ### [数据格式定义] 263 | 数据格式 264 | 265 | |字段名|数据类型|是否必填/默认值|NULL|其他| 266 | |:--|:-:|:-:|:-:|:-:| 267 | |id__in | 列表 | 非必填/ | False | [ - 详见下方表格](#mjhjnvfejyeyblouxsqztdccbonrwtdf) | 268 | 269 | id__in 270 | 271 | |字段名|数据类型|是否必填/默认值|NULL|其他| 272 | |:--|:-:|:-:|:-:|:-:| 273 | |子字段 | 整型数字 | 必填 | False | - | 274 | 275 | 276 | 277 | ### - 删除一个报告结果 278 | 279 | 280 | 281 | 请求 282 | 283 | ```js 284 | { 285 | "id": [ 286 | 2 287 | ] 288 | } 289 | ``` 290 | 291 | 响应 292 | 293 | ```js 294 | { 295 | "err": null, 296 | "data": null, 297 | "msg": null 298 | } 299 | ``` 300 | 301 | -------------------------------------------------------------------------------- /series_10/api_doc/node.md: -------------------------------------------------------------------------------- 1 | # /api/v1/node/config 2 | 3 | ## GET 4 | 5 | 6 | 7 | ### 获取提供给 minion 的配置 8 | 9 | 10 | 11 | 响应 12 | 13 | ```js 14 | { 15 | "err": null, 16 | "msg": "", 17 | "data": { 18 | "services_config": { 19 | "tengine": { 20 | "version": 6, 21 | "config": [ 22 | { 23 | "sites": [ 24 | { 25 | "domains": [ 26 | "*" 27 | ], 28 | "backend": { 29 | "servers": [ 30 | { 31 | "address": "127.0.0.1", 32 | "port": 8080, 33 | "weight": 1 34 | } 35 | ], 36 | "health_check": {} 37 | }, 38 | "ssl_options": {}, 39 | "reverse_proxy_options": {}, 40 | "listen_options": [ 41 | { 42 | "port": 80, 43 | "address": "0.0.0.0" 44 | } 45 | ], 46 | "client_ip_source": "remote_addr", 47 | "forbidden_page_response_code": 403, 48 | "name": "68", 49 | "forbidden_page_content": "", 50 | "x_forwarded_for_option": "AppendXFFHeader" 51 | } 52 | ], 53 | "interface_name": "br0" 54 | }, 55 | { 56 | "interface_name": "br1" 57 | }, 58 | { 59 | "interface_name": "br2" 60 | }, 61 | { 62 | "interface_name": "br3" 63 | }, 64 | { 65 | "interface_name": "br4" 66 | } 67 | ] 68 | } 69 | } 70 | } 71 | } 72 | ``` 73 | 74 | ## POST 75 | 76 | 数据格式 77 | 78 | |字段名|数据类型|是否必填/默认值|NULL|其他| 79 | |:--|:-:|:-:|:-:|:-:| 80 | |id | 字符串 | 必填 | False | - | 81 | |services_status | 字典 | - | - | 详见下面表格 | 82 | 83 | services_status 84 | 85 | |字段名|数据类型|是否必填/默认值|NULL|其他| 86 | |:--|:-:|:-:|:-:|:-:| 87 | |tengine | 字典 | - | - | 详见下面表格 | 88 | 89 | tengine 90 | 91 | |字段名|数据类型|是否必填/默认值|NULL|其他| 92 | |:--|:-:|:-:|:-:|:-:| 93 | |version | 整型数字 | 必填 | False | - | 94 | |status | 字典 | - | - | 详见下面表格 | 95 | 96 | status 97 | 98 | |字段名|数据类型|是否必填/默认值|NULL|其他| 99 | |:--|:-:|:-:|:-:|:-:| 100 | |status | 字符串 | 必填 | False | - | 101 | |last_result | 字符串 | 必填 | False | 最小长度: 0; | 102 | |sites | 字典类型 | - | - | 字典类型 | 103 | 104 | sites 105 | 106 | |字段名|数据类型|是否必填/默认值|NULL|其他| 107 | |:--|:-:|:-:|:-:|:-:| 108 | |子字段 | 字典 | - | - | 详见下面表格 | 109 | 110 | 子字段 111 | 112 | |字段名|数据类型|是否必填/默认值|NULL|其他| 113 | |:--|:-:|:-:|:-:|:-:| 114 | |status | 字符串 | 必填 | False | - | 115 | |upstreams | 字典类型 | - | - | 字典类型 | 116 | 117 | upstreams 118 | 119 | |字段名|数据类型|是否必填/默认值|NULL|其他| 120 | |:--|:-:|:-:|:-:|:-:| 121 | |子字段 | 字符串 | 必填 | False | - | 122 | 123 | 124 | 125 | ### minion 报告状态获取节点配置 126 | 127 | 128 | 129 | 请求 130 | 131 | ```js 132 | { 133 | "id": "test", 134 | "services_status": { 135 | "tengine": { 136 | "version": 0, 137 | "status": { 138 | "status": "HEALTHY", 139 | "last_result": "", 140 | "sites": { 141 | "69": { 142 | "status": "HEALTHY", 143 | "upstreams": { 144 | "backend1.com:80": "HEALTHY", 145 | "backend1.com:90": "HEALTHY" 146 | } 147 | } 148 | } 149 | } 150 | } 151 | } 152 | } 153 | ``` 154 | 155 | 响应 156 | 157 | ```js 158 | { 159 | "err": null, 160 | "msg": "", 161 | "data": { 162 | "services_config": { 163 | "tengine": { 164 | "version": 6, 165 | "config": [ 166 | { 167 | "sites": [ 168 | { 169 | "domains": [ 170 | "*" 171 | ], 172 | "backend": { 173 | "servers": [ 174 | { 175 | "address": "127.0.0.1", 176 | "port": 8080, 177 | "weight": 1 178 | } 179 | ], 180 | "health_check": {} 181 | }, 182 | "ssl_options": {}, 183 | "reverse_proxy_options": {}, 184 | "listen_options": [ 185 | { 186 | "port": 80, 187 | "address": "0.0.0.0" 188 | } 189 | ], 190 | "client_ip_source": "remote_addr", 191 | "forbidden_page_response_code": 403, 192 | "name": "69", 193 | "forbidden_page_content": "", 194 | "x_forwarded_for_option": "AppendXFFHeader" 195 | } 196 | ], 197 | "interface_name": "br0" 198 | }, 199 | { 200 | "interface_name": "br1" 201 | }, 202 | { 203 | "interface_name": "br2" 204 | }, 205 | { 206 | "interface_name": "br3" 207 | }, 208 | { 209 | "interface_name": "br4" 210 | } 211 | ] 212 | } 213 | } 214 | } 215 | } 216 | ``` 217 | 218 | -------------------------------------------------------------------------------- /series_20/api_doc/node.md: -------------------------------------------------------------------------------- 1 | # /api/NodeInfoAPI 2 | 3 | ## GET 4 | 5 | ### 权限 6 | - System Monitor 7 | 8 | ### - 获取节点数据 9 | 10 | 11 | 12 | 响应 13 | 14 | ```js 15 | { 16 | "err": null, 17 | "data": [ 18 | { 19 | "id": "minion", 20 | "node": { 21 | "timestamp": 1544513539, 22 | "cpu": 0.02, 23 | "mem": 0.02, 24 | "uptime": 100000, 25 | "network_io": [ 26 | { 27 | "time_from": 150000000, 28 | "time_to": 150000000, 29 | "interface": "lo", 30 | "read": 255.0, 31 | "write": 825.0 32 | } 33 | ], 34 | "disk_info": { 35 | "time_from": 150000000, 36 | "time_to": 150000000, 37 | "total": 100, 38 | "usage": 12, 39 | "read": 1000, 40 | "write": 1000 41 | }, 42 | "detector": { 43 | "node": "deploy", 44 | "time_from": 1526541111.271, 45 | "time_to": 1526541116.272, 46 | "ioread": 554, 47 | "iowrite": 651, 48 | "detector_version": "9", 49 | "policy_version": { 50 | "current_version": "1" 51 | }, 52 | "req_num_total": 10086, 53 | "rsp_num_total": 0, 54 | "req_num_blocked": 0, 55 | "req_num_blocked_acl": 0, 56 | "req_num_blocked_web": 0, 57 | "req_num_attack": 0, 58 | "req_num_attack_acl": 0, 59 | "req_num_attack_web": 0, 60 | "rsp_num_blocked": 0, 61 | "rsp_num_blocked_acl": 0, 62 | "rsp_num_blocked_web": 0, 63 | "rsp_num_attack": 0, 64 | "rsp_num_attack_acl": 0, 65 | "rsp_num_attack_web": 0, 66 | "req_detect_time": 0, 67 | "req_detect_count": 0, 68 | "rsp_detect_time": 0, 69 | "rsp_detect_count": 0, 70 | "req_ngx_time_distribution": { 71 | "tengine-1": { 72 | "time": 4803.0, 73 | "count": 6 74 | } 75 | } 76 | }, 77 | "tengine": { 78 | "config_version": 1, 79 | "stats": { 80 | "time_from": 150000000, 81 | "time_to": 150000000, 82 | "accepts": 100, 83 | "handled": 100, 84 | "requests": 100, 85 | "avg_time": 800.5 86 | }, 87 | "hostname": "tengine-1" 88 | }, 89 | "mario": null, 90 | "last_timestamp": -1, 91 | "status": "STARTING", 92 | "received_timestamp": 1544513539.213878 93 | }, 94 | "ha": { 95 | "services": [ 96 | { 97 | "name": "mgt-api", 98 | "healthy": true, 99 | "estate": "master", 100 | "mstate": "idle" 101 | }, 102 | { 103 | "name": "tengine", 104 | "healthy": false, 105 | "estate": "slave", 106 | "mstate": "idle" 107 | } 108 | ] 109 | }, 110 | "services": [ 111 | { 112 | "name": "mgt-api", 113 | "hostname": "mgt-api-ilibili-slb1", 114 | "version": "18.02.1", 115 | "status": "HEALTHY", 116 | "cpu": 0.02, 117 | "mem": 0.02, 118 | "uptime": 100000 119 | }, 120 | { 121 | "name": "tengine", 122 | "hostname": "tengine-1", 123 | "version": "18.02.1", 124 | "status": "HEALTHY", 125 | "cpu": 0.02, 126 | "mem": 0.02, 127 | "uptime": 100000, 128 | "status_hint": "" 129 | }, 130 | { 131 | "name": "detector-srv", 132 | "hostname": "detector-srv", 133 | "version": "18.02.1", 134 | "status": "HEALTHY", 135 | "cpu": 0.02, 136 | "mem": 0.02, 137 | "uptime": 100000, 138 | "status_hint": "" 139 | } 140 | ], 141 | "sites": [] 142 | } 143 | ], 144 | "msg": null 145 | } 146 | ``` 147 | 148 | # /api/ServerTimeAPI 149 | 150 | ## GET 151 | 152 | ### 权限 153 | - System Management 154 | 155 | ### - 获取服务器时间 156 | 157 | 158 | 159 | 响应 160 | 161 | ```js 162 | { 163 | "err": null, 164 | "data": [ 165 | { 166 | "node": "ws-test-client", 167 | "data": { 168 | "err": null, 169 | "data": 1533711846 170 | }, 171 | "channel_name": "specific.aaa" 172 | } 173 | ], 174 | "msg": null 175 | } 176 | ``` 177 | 178 | ## PUT 179 | 180 | ### [数据格式定义] 181 | 数据格式 182 | 183 | |字段名|数据类型|是否必填/默认值|NULL|其他| 184 | |:--|:-:|:-:|:-:|:-:| 185 | |value | 浮点数字 | 必填 | False | - | 186 | 187 | ### 权限 188 | - System Management 189 | 190 | ### - 设置服务器时间 191 | 192 | 193 | 194 | 请求 195 | 196 | ```js 197 | { 198 | "value": 1533711846 199 | } 200 | ``` 201 | 202 | 响应 203 | 204 | ```js 205 | { 206 | "err": null, 207 | "data": null, 208 | "msg": null 209 | } 210 | ``` 211 | 212 | # /api/HACommandAPI 213 | 214 | ## POST 215 | 216 | ### [数据格式定义] 217 | 数据格式 218 | 219 | |字段名|数据类型|是否必填/默认值|NULL|其他| 220 | |:--|:-:|:-:|:-:|:-:| 221 | |node | 字符串 | 必填 | False | - | 222 | |service | 字符串 | 必填 | False | - | 223 | |command | 指定选项 | 必填 | False | 选项是: ['recover', 'halt'] | 224 | 225 | ### 权限 226 | - System Management 227 | 228 | ### - HA service 重连 229 | 230 | 231 | 232 | 请求 233 | 234 | ```js 235 | { 236 | "command": "recover", 237 | "node": "minion-1", 238 | "service": "mgt-api" 239 | } 240 | ``` 241 | 242 | 响应 243 | 244 | ```js 245 | { 246 | "err": null, 247 | "data": null, 248 | "msg": null 249 | } 250 | ``` 251 | 252 | -------------------------------------------------------------------------------- /assets/safeline_logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Slice 7 5 | Created with Sketch. 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /series_20/api_doc/mario.md: -------------------------------------------------------------------------------- 1 | # /api/MarioPluginAPI 2 | 3 | ## GET 4 | 5 | ### 权限 6 | - Website And Security Policy Management 7 | 8 | ### - 获取插件信息列表 9 | 10 | 11 | 12 | ?> 本 API 支持翻页参数 13 | 14 | 响应 15 | 16 | ```js 17 | { 18 | "err": null, 19 | "data": [ 20 | { 21 | "id": 9, 22 | "state": "running", 23 | "statistic": { 24 | "total_execution": 123.0, 25 | "total_error_execution": 123.0, 26 | "execution_per_second": 123.5, 27 | "average_execution_time": 0.007, 28 | "error_execution_per_second": 123.5, 29 | "timestamp": 1544513537.0 30 | }, 31 | "name": "foobar", 32 | "comment": "comment", 33 | "create_time": "1544513537", 34 | "last_update_time": "1544513537", 35 | "is_enabled": true, 36 | "code": "print('Hello World!')", 37 | "hash": "e0906b1de689ae9a9daa3ed52d8ee0ac" 38 | } 39 | ], 40 | "msg": null 41 | } 42 | ``` 43 | 44 | ## POST 45 | 46 | ### [数据格式定义] 47 | 数据格式 48 | 49 | |字段名|数据类型|是否必填/默认值|NULL|其他| 50 | |:--|:-:|:-:|:-:|:-:| 51 | |name | 字符串 | 必填 | False | - | 52 | |comment | 字符串 | 非必填/ | False | 最小长度: 0; | 53 | |is_enabled | 布尔 | 必填 | False | - | 54 | |code | 字符串 | 必填 | False | lua 脚本; | 55 | 56 | ### 权限 57 | - Website And Security Policy Management 58 | 59 | ### - 创建一个插件 60 | 61 | 62 | 63 | 请求 64 | 65 | ```js 66 | { 67 | "name": "foobar", 68 | "comment": "comment", 69 | "is_enabled": true, 70 | "code": "print('Hello World!')" 71 | } 72 | ``` 73 | 74 | 响应 75 | 76 | ```js 77 | { 78 | "err": null, 79 | "data": { 80 | "id": 5, 81 | "state": "unknown", 82 | "statistic": { 83 | "total_execution": 0.0, 84 | "total_error_execution": 0.0, 85 | "execution_per_second": 0.0, 86 | "average_execution_time": 0.0, 87 | "error_execution_per_second": 0.0, 88 | "timestamp": 0.0 89 | }, 90 | "name": "foobar", 91 | "comment": "comment", 92 | "create_time": "1544513536", 93 | "last_update_time": "1544513536", 94 | "is_enabled": true, 95 | "code": "print('Hello World!')", 96 | "hash": "e0906b1de689ae9a9daa3ed52d8ee0ac" 97 | }, 98 | "msg": null 99 | } 100 | ``` 101 | 102 | ## PUT 103 | 104 | ### [数据格式定义] 105 | 数据格式 106 | 107 | |字段名|数据类型|是否必填/默认值|NULL|其他| 108 | |:--|:-:|:-:|:-:|:-:| 109 | |id | 整型数字 | 必填 | False | - | 110 | |name | 字符串 | 必填 | False | - | 111 | |comment | 字符串 | 非必填/ | False | 最小长度: 0; | 112 | |is_enabled | 布尔 | 必填 | False | - | 113 | |code | 字符串 | 必填 | False | lua 脚本; | 114 | 115 | ### 权限 116 | - Website And Security Policy Management 117 | 118 | ### - 修改一个插件的信息 119 | 120 | 121 | 122 | 请求 123 | 124 | ```js 125 | { 126 | "name": "new_foobar", 127 | "comment": "comment", 128 | "is_enabled": true, 129 | "code": "print('test_edit_mario_plugin')", 130 | "id": 7 131 | } 132 | ``` 133 | 134 | 响应 135 | 136 | ```js 137 | { 138 | "err": null, 139 | "data": { 140 | "id": 7, 141 | "state": "unknown", 142 | "statistic": { 143 | "total_execution": 0.0, 144 | "total_error_execution": 0.0, 145 | "execution_per_second": 0.0, 146 | "average_execution_time": 0.0, 147 | "error_execution_per_second": 0.0, 148 | "timestamp": 0.0 149 | }, 150 | "name": "new_foobar", 151 | "comment": "comment", 152 | "create_time": "1544513537", 153 | "last_update_time": "1544513537", 154 | "is_enabled": true, 155 | "code": "print('test_edit_mario_plugin')", 156 | "hash": "1112745ea3468f8485bb054701b74be4" 157 | }, 158 | "msg": null 159 | } 160 | ``` 161 | 162 | ## DELETE 163 | 164 | ### [数据格式定义] 165 | 数据格式 166 | 167 | |字段名|数据类型|是否必填/默认值|NULL|其他| 168 | |:--|:-:|:-:|:-:|:-:| 169 | |id__in | 列表 | 非必填/ | False | [ - 详见下方表格](#xageqgfpcyiomciyldcwxqkytpvoxysn) | 170 | 171 | id__in 172 | 173 | |字段名|数据类型|是否必填/默认值|NULL|其他| 174 | |:--|:-:|:-:|:-:|:-:| 175 | |子字段 | 整型数字 | 必填 | False | - | 176 | 177 | ### 权限 178 | - Website And Security Policy Management 179 | 180 | ### - 删除一个插件 181 | 182 | 183 | 184 | 请求 185 | 186 | ```js 187 | { 188 | "id": 6 189 | } 190 | ``` 191 | 192 | 响应 193 | 194 | ```js 195 | { 196 | "err": null, 197 | "data": null, 198 | "msg": null 199 | } 200 | ``` 201 | 202 | # /api/MarioPluginStateUpdateAPI 203 | 204 | ## POST 205 | 206 | ### [数据格式定义] 207 | 数据格式 208 | 209 | |字段名|数据类型|是否必填/默认值|NULL|其他| 210 | |:--|:-:|:-:|:-:|:-:| 211 | |timestamp | 浮点数字 | 必填 | False | - | 212 | |id | 字符串 | 必填 | False | - | 213 | |state | 指定选项 | 必填 | False | 选项是: ['running', 'compile-error'] | 214 | |total_execution | 整型数字 | 必填 | False | - | 215 | |total_error_execution | 整型数字 | 必填 | False | - | 216 | |execution_per_second | 浮点数字 | 必填 | False | - | 217 | |error_execution_per_second | 浮点数字 | 必填 | False | - | 218 | |average_execution_time | 浮点数字 | 必填 | False | - | 219 | 220 | ### 权限 221 | - Minion 222 | 223 | ### - 插件状态更新 224 | 225 | 226 | 227 | 请求 228 | 229 | ```js 230 | [ 231 | { 232 | "timestamp": 1544513537, 233 | "id": 10, 234 | "state": "running", 235 | "total_execution": 123, 236 | "total_error_execution": 123, 237 | "execution_per_second": 123.5, 238 | "error_execution_per_second": 123.5, 239 | "average_execution_time": 0.007 240 | } 241 | ] 242 | ``` 243 | 244 | 响应 245 | 246 | ```js 247 | { 248 | "err": null, 249 | "data": null, 250 | "msg": null 251 | } 252 | ``` 253 | 254 | # /api/PluginLogAPI 255 | 256 | ## GET 257 | 258 | ### [数据格式定义] 259 | 数据格式 260 | 261 | |字段名|数据类型|是否必填/默认值|NULL|其他| 262 | |:--|:-:|:-:|:-:|:-:| 263 | |id__in | 列表 | 非必填/ | False | [ - 详见下方表格](#grujmzwrfudpeebakdozancmdqlnnugs) | 264 | |timestamp__range | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#mpogkbwxumdbnnuyxzniqxfxqyilowhq) | 265 | |plugin_id | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#vybkwoncchnjlvfnwvneoakmumiaslrx) | 266 | |tag__like | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#zghrtwgurvydlmahckxuhaiwxvypzahq) | 267 | |content__like | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#jhswphxsdjjatywrzpnljvedkqdeuexe) | 268 | 269 | id__in 270 | 271 | |字段名|数据类型|是否必填/默认值|NULL|其他| 272 | |:--|:-:|:-:|:-:|:-:| 273 | |子字段 | 整型数字 | 必填 | False | - | 274 | 275 | 276 | timestamp__range 277 | 278 | |字段名|数据类型|是否必填/默认值|NULL|其他| 279 | |:--|:-:|:-:|:-:|:-:| 280 | |子字段 | 字符串 | 必填 | False | 时间范围,比如 1234-4567, 1234-0; | 281 | 282 | 283 | plugin_id 284 | 285 | |字段名|数据类型|是否必填/默认值|NULL|其他| 286 | |:--|:-:|:-:|:-:|:-:| 287 | |子字段 | 整型数字 | 必填 | False | - | 288 | 289 | 290 | tag__like 291 | 292 | |字段名|数据类型|是否必填/默认值|NULL|其他| 293 | |:--|:-:|:-:|:-:|:-:| 294 | |子字段 | 字符串 | 必填 | False | - | 295 | 296 | 297 | content__like 298 | 299 | |字段名|数据类型|是否必填/默认值|NULL|其他| 300 | |:--|:-:|:-:|:-:|:-:| 301 | |子字段 | 字符串 | 必填 | False | - | 302 | 303 | ### 权限 304 | - Website And Security Policy Management 305 | 306 | ### - 获取插件信息列表 307 | 308 | 309 | 310 | ?> 本 API 支持翻页参数 311 | 312 | 请求 313 | 314 | ```js 315 | { 316 | "plugin_id": [ 317 | "1" 318 | ], 319 | "tag__like": [ 320 | "t" 321 | ], 322 | "content__like": [ 323 | "c" 324 | ], 325 | "timestamp__range": [ 326 | "1544513438-1544513538" 327 | ] 328 | } 329 | ``` 330 | 331 | 响应 332 | 333 | ```js 334 | { 335 | "err": null, 336 | "data": [], 337 | "msg": null 338 | } 339 | ``` 340 | 341 | # /api/DownloadPluginLogAPI 342 | 343 | ## POST 344 | 345 | ### [数据格式定义] 346 | 数据格式 347 | 348 | |字段名|数据类型|是否必填/默认值|NULL|其他| 349 | |:--|:-:|:-:|:-:|:-:| 350 | |id__in | 列表 | 非必填/ | False | [ - 详见下方表格](#ujrlngizbotoaguqwitnevlelwwbllvn) | 351 | |timestamp__range | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#krzymkipklphnkrixnkqnizzookbjtin) | 352 | |plugin_id | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#twlgkxqcncnjqbryofxlpklmwyzfkocj) | 353 | |tag__like | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#cbchbnrhfnkcmugcuzlsldxgyuwkllsa) | 354 | |content__like | 列表 | 非必填/无默认值 | False | [ - 详见下方表格](#vrlbcbgteivvdticnpkviwzxcidpcuzk) | 355 | 356 | id__in 357 | 358 | |字段名|数据类型|是否必填/默认值|NULL|其他| 359 | |:--|:-:|:-:|:-:|:-:| 360 | |子字段 | 整型数字 | 必填 | False | - | 361 | 362 | 363 | timestamp__range 364 | 365 | |字段名|数据类型|是否必填/默认值|NULL|其他| 366 | |:--|:-:|:-:|:-:|:-:| 367 | |子字段 | 字符串 | 必填 | False | 时间范围,比如 1234-4567, 1234-0; | 368 | 369 | 370 | plugin_id 371 | 372 | |字段名|数据类型|是否必填/默认值|NULL|其他| 373 | |:--|:-:|:-:|:-:|:-:| 374 | |子字段 | 整型数字 | 必填 | False | - | 375 | 376 | 377 | tag__like 378 | 379 | |字段名|数据类型|是否必填/默认值|NULL|其他| 380 | |:--|:-:|:-:|:-:|:-:| 381 | |子字段 | 字符串 | 必填 | False | - | 382 | 383 | 384 | content__like 385 | 386 | |字段名|数据类型|是否必填/默认值|NULL|其他| 387 | |:--|:-:|:-:|:-:|:-:| 388 | |子字段 | 字符串 | 必填 | False | - | 389 | 390 | ### 权限 391 | - Website And Security Policy Management 392 | 393 | ### - 下载插件日志 394 | 395 | 396 | 397 | 请求 398 | 399 | ```js 400 | { 401 | "tag__like": [ 402 | "t" 403 | ], 404 | "content__like": [ 405 | "c" 406 | ], 407 | "timestamp__range": [ 408 | "1544513435-1544513535" 409 | ] 410 | } 411 | ``` 412 | 413 | 响应 414 | 415 | ```js 416 | { 417 | "err": null, 418 | "data": null, 419 | "msg": null 420 | } 421 | ``` 422 | 423 | -------------------------------------------------------------------------------- /series_10/api_doc/mario.md: -------------------------------------------------------------------------------- 1 | # /api/v1/mario/plugin 2 | 3 | ## GET 4 | 5 | 6 | 7 | ### 获取 mario 插件 8 | 9 | 10 | 11 | 本 API 支持翻页参数 12 | 13 | 响应 14 | 15 | ```js 16 | { 17 | "err": null, 18 | "msg": "", 19 | "data": [ 20 | { 21 | "id": "bbe6087e83324efba5ef7fec1c59ff95", 22 | "create_time": 1545297433.275232, 23 | "update_time": 1545297433.275248, 24 | "statistic": { 25 | "total_execution": 0.0, 26 | "total_error_execution": 0.0, 27 | "execution_per_second": 0.0, 28 | "average_execution_time": 0.0, 29 | "error_execution_per_second": 0.0, 30 | "timestamp": 0.0 31 | }, 32 | "running_state": "unknown", 33 | "name": "plugin1", 34 | "comment": "", 35 | "type": "user_plugin", 36 | "state": "enabled", 37 | "code": "print(\"test\")", 38 | "hash": "231496871f9a2ed5404086f91b6ef55f" 39 | } 40 | ] 41 | } 42 | ``` 43 | 44 | ## POST 45 | 46 | 数据格式 47 | 48 | |字段名|数据类型|是否必填/默认值|NULL|其他| 49 | |:--|:-:|:-:|:-:|:-:| 50 | |name | 字符串 | 必填 | False | 最大长度: 64; | 51 | |comment | 字符串 | 非必填/'' | False | 最小长度: 0; | 52 | |state | 指定选项 | 必填 | False | 选项是: ['enabled', 'disabled'] | 53 | |code | 字符串 | 必填 | False | lua 脚本; | 54 | 55 | 56 | 57 | ### 新建 mario 插件 58 | 59 | 60 | 61 | 请求 62 | 63 | ```js 64 | { 65 | "name": "plugin1", 66 | "state": "enabled", 67 | "code": "print(\"test\")" 68 | } 69 | ``` 70 | 71 | 响应 72 | 73 | ```js 74 | { 75 | "err": null, 76 | "msg": "", 77 | "data": { 78 | "id": "85d81d46fc474edf8311bdeb2c597668", 79 | "create_time": 1545297433.000389, 80 | "update_time": 1545297433.000405, 81 | "statistic": { 82 | "total_execution": 0.0, 83 | "total_error_execution": 0.0, 84 | "execution_per_second": 0.0, 85 | "average_execution_time": 0.0, 86 | "error_execution_per_second": 0.0, 87 | "timestamp": 0.0 88 | }, 89 | "running_state": "unknown", 90 | "name": "plugin1", 91 | "comment": "", 92 | "type": "user_plugin", 93 | "state": "enabled", 94 | "code": "print(\"test\")", 95 | "hash": "231496871f9a2ed5404086f91b6ef55f" 96 | } 97 | } 98 | ``` 99 | 100 | ## PUT 101 | 102 | 数据格式 103 | 104 | |字段名|数据类型|是否必填/默认值|NULL|其他| 105 | |:--|:-:|:-:|:-:|:-:| 106 | |name | 字符串 | 必填 | False | 最大长度: 64; | 107 | |comment | 字符串 | 非必填/'' | False | 最小长度: 0; | 108 | |state | 指定选项 | 必填 | False | 选项是: ['enabled', 'disabled'] | 109 | |code | 字符串 | 必填 | False | lua 脚本; | 110 | |id | UUID | 必填 | False | UUID4 | 111 | 112 | 113 | 114 | ### 修改 mario 插件 115 | 116 | 117 | 118 | 请求 119 | 120 | ```js 121 | { 122 | "name": "plugin1", 123 | "state": "enabled", 124 | "code": "print(\"test\")", 125 | "id": "7da7a4c1933c42be90f629d5ca8dc0fd" 126 | } 127 | ``` 128 | 129 | 响应 130 | 131 | ```js 132 | { 133 | "err": null, 134 | "msg": "", 135 | "data": { 136 | "id": "7da7a4c1933c42be90f629d5ca8dc0fd", 137 | "create_time": 1545297433.415531, 138 | "update_time": 1545297433.429221, 139 | "statistic": { 140 | "total_execution": 0.0, 141 | "total_error_execution": 0.0, 142 | "execution_per_second": 0.0, 143 | "average_execution_time": 0.0, 144 | "error_execution_per_second": 0.0, 145 | "timestamp": 0.0 146 | }, 147 | "running_state": "unknown", 148 | "name": "plugin1", 149 | "comment": "", 150 | "type": "user_plugin", 151 | "state": "enabled", 152 | "code": "print(\"test\")", 153 | "hash": "231496871f9a2ed5404086f91b6ef55f" 154 | } 155 | } 156 | ``` 157 | 158 | ## DELETE 159 | 160 | 数据格式 161 | 162 | |字段名|数据类型|是否必填/默认值|NULL|其他| 163 | |:--|:-:|:-:|:-:|:-:| 164 | |id | UUID | 必填 | False | UUID4 | 165 | 166 | 167 | 168 | ### 删除 mario 插件 169 | 170 | 171 | 172 | 请求 173 | 174 | ```js 175 | { 176 | "id": "6ff551dd85f54865a58df83e0de026e7" 177 | } 178 | ``` 179 | 180 | 响应 181 | 182 | ```js 183 | { 184 | "err": null, 185 | "msg": "", 186 | "data": null 187 | } 188 | ``` 189 | 190 | # /api/v1/mario/plugin/state 191 | 192 | ## POST 193 | 194 | 数据格式 195 | 196 | |字段名|数据类型|是否必填/默认值|NULL|其他| 197 | |:--|:-:|:-:|:-:|:-:| 198 | |timestamp | 浮点数字 | 必填 | False | - | 199 | |id | UUID | 必填 | False | UUID4 | 200 | |hash | 字符串 | 必填 | False | 最大长度: 32; | 201 | |state | 字符串 | 必填 | False | 最大长度: 32; | 202 | |total_execution | 整型数字 | 必填 | False | - | 203 | |total_error_execution | 整型数字 | 必填 | False | - | 204 | |execution_per_second | 整型数字 | 必填 | False | - | 205 | |error_execution_per_second | 整型数字 | 必填 | False | - | 206 | |average_execution_time | 浮点数字 | 必填 | False | - | 207 | 208 | 209 | 210 | ### mario 更新插件状态 211 | 212 | 213 | 214 | 请求 215 | 216 | ```js 217 | [ 218 | { 219 | "timestamp": 1545297433, 220 | "id": "325e4a11a0994053b918d19b14214648", 221 | "hash": "cfcd208495d565ef66e7dff9f98764da", 222 | "state": "running", 223 | "total_execution": 123, 224 | "total_error_execution": 123, 225 | "execution_per_second": 123, 226 | "error_execution_per_second": 123, 227 | "average_execution_time": 0.007 228 | }, 229 | { 230 | "timestamp": 1545297433, 231 | "id": "78eb088083174a5cbb5cafd9222b9616", 232 | "hash": "c4ca4238a0b923820dcc509a6f75849b", 233 | "state": "running", 234 | "total_execution": 123, 235 | "total_error_execution": 123, 236 | "execution_per_second": 123, 237 | "error_execution_per_second": 123, 238 | "average_execution_time": 0.007 239 | }, 240 | { 241 | "timestamp": 1545297433, 242 | "id": "0258fce859254377907de1a255d2975a", 243 | "hash": "c81e728d9d4c2f636f067f89cc14862c", 244 | "state": "running", 245 | "total_execution": 123, 246 | "total_error_execution": 123, 247 | "execution_per_second": 123, 248 | "error_execution_per_second": 123, 249 | "average_execution_time": 0.007 250 | }, 251 | { 252 | "timestamp": 1545297433, 253 | "id": "7fabee0b40a3498b910c18ccffff16c1", 254 | "hash": "eccbc87e4b5ce2fe28308fd9f2a7baf3", 255 | "state": "running", 256 | "total_execution": 123, 257 | "total_error_execution": 123, 258 | "execution_per_second": 123, 259 | "error_execution_per_second": 123, 260 | "average_execution_time": 0.007 261 | } 262 | ] 263 | ``` 264 | 265 | 响应 266 | 267 | ```js 268 | { 269 | "err": null, 270 | "msg": "", 271 | "data": [ 272 | { 273 | "id": "325e4a11a0994053b918d19b14214648", 274 | "action": "nop", 275 | "code": "0" 276 | }, 277 | { 278 | "id": "78eb088083174a5cbb5cafd9222b9616", 279 | "action": "update", 280 | "code": "new code" 281 | }, 282 | { 283 | "id": "0258fce859254377907de1a255d2975a", 284 | "action": "delete", 285 | "code": "" 286 | }, 287 | { 288 | "id": "7fabee0b40a3498b910c18ccffff16c1", 289 | "action": "delete", 290 | "code": "" 291 | }, 292 | { 293 | "id": "edf14336e5a344eea6a54e74b47f587a", 294 | "action": "new", 295 | "code": "4" 296 | } 297 | ] 298 | } 299 | ``` 300 | 301 | # /api/v1/mario/plugin/state/log 302 | 303 | ## POST 304 | 305 | 数据格式 306 | 307 | |字段名|数据类型|是否必填/默认值|NULL|其他| 308 | |:--|:-:|:-:|:-:|:-:| 309 | |timestamp | 浮点数字 | 必填 | False | - | 310 | |id | UUID | 必填 | False | UUID4 | 311 | |tag | 字符串 | 必填 | False | 最大长度: 32; | 312 | |msg | 字符串 | 必填 | False | - | 313 | 314 | 315 | 316 | ### mario 更新 plugin 日志 317 | 318 | 319 | 320 | 请求 321 | 322 | ```js 323 | [ 324 | { 325 | "timestamp": 1545297434, 326 | "id": "a7276226aa5c414196b02f1d332b8ea9", 327 | "tag": "system", 328 | "msg": "test msg" 329 | }, 330 | { 331 | "timestamp": 1545297434, 332 | "id": "d5ebeaa7974f461891ece33a48e34fc7", 333 | "tag": "system", 334 | "msg": "test msg" 335 | } 336 | ] 337 | ``` 338 | 339 | 响应 340 | 341 | ```js 342 | { 343 | "err": null, 344 | "msg": "", 345 | "data": null 346 | } 347 | ``` 348 | 349 | # /api/v1/mario/plugin/log 350 | 351 | ## GET 352 | 353 | 数据格式 354 | 355 | |字段名|数据类型|是否必填/默认值|NULL|其他| 356 | |:--|:-:|:-:|:-:|:-:| 357 | |time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | 358 | |time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | 359 | |plugin_id | 字符串 | 必填 | False | - | 360 | |tag | 字符串 | 非必填/无默认值 | False | - | 361 | 362 | 363 | 364 | ### 获取插件 log 365 | 366 | 367 | 368 | 本 API 支持翻页参数 369 | 370 | 请求 371 | 372 | ```js 373 | { 374 | "plugin_id": "91a1769c6d6d4fe5bc39e5e00aa661fa", 375 | "count": 2, 376 | "offset": 1, 377 | "time_before": 1545297433.688, 378 | "time_after": 1545297430.688 379 | } 380 | ``` 381 | 382 | 响应 383 | 384 | ```js 385 | { 386 | "err": null, 387 | "msg": "", 388 | "data": { 389 | "items": [ 390 | { 391 | "id": 8, 392 | "timestamp": 1545297431.688, 393 | "tag": "", 394 | "msg": "" 395 | }, 396 | { 397 | "id": 9, 398 | "timestamp": 1545297430.688, 399 | "tag": "", 400 | "msg": "" 401 | } 402 | ], 403 | "total": 3 404 | } 405 | } 406 | ``` 407 | 408 | ## DELETE 409 | 410 | 数据格式 411 | 412 | |字段名|数据类型|是否必填/默认值|NULL|其他| 413 | |:--|:-:|:-:|:-:|:-:| 414 | |time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | 415 | |time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | 416 | |plugin_id | 字符串 | 必填 | False | - | 417 | |tag | 字符串 | 非必填/无默认值 | False | - | 418 | 419 | 420 | 421 | ### 删除插件 log 422 | 423 | 424 | 425 | 请求 426 | 427 | ```js 428 | { 429 | "plugin_id": "1df641ff9ecb488d980dda1553bc7e2b" 430 | } 431 | ``` 432 | 433 | 响应 434 | 435 | ```js 436 | { 437 | "err": null, 438 | "msg": "", 439 | "data": null 440 | } 441 | ``` 442 | 443 | # /api/v1/mario/plugin/log/download 444 | 445 | ## GET 446 | 447 | 数据格式 448 | 449 | |字段名|数据类型|是否必填/默认值|NULL|其他| 450 | |:--|:-:|:-:|:-:|:-:| 451 | |time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | 452 | |time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | 453 | |plugin_id | 字符串 | 必填 | False | - | 454 | |tag | 字符串 | 非必填/无默认值 | False | - | 455 | 456 | 457 | 458 | ### 下载插件 log 459 | 460 | 461 | 462 | 请求 463 | 464 | ```js 465 | { 466 | "plugin_id": "b0a6c5863e024c1381c2671d917511f4" 467 | } 468 | ``` 469 | 470 | 响应 471 | 472 | ```js 473 | "不支持展示的数据类型,如文件下载等" 474 | ``` 475 | 476 | -------------------------------------------------------------------------------- /series_10/plugin_sdk.md: -------------------------------------------------------------------------------- 1 | # SafeLine 10 扩展插件文档 2 | 3 | SafeLine 扩展插件系统(以下简称插件系统)使用 Lua 作为脚本语言,**目前语法兼容标准 Lua 5.1。** 4 | 5 | 在「入侵检测」→「扩展插件管理」页面可对插件进行新增,查看,删除等操作。本文档将说明插件的编写方法以及可用的相关接口。 6 | 7 | 8 | 9 | ## 导入 10 | 11 | 在编写插件时,首先需要导入 skynet 模块,skynet 模块包含了插件系统提供的所有接口。 12 | 13 | ```lua 14 | local skynet = require "skynet" 15 | ``` 16 | 17 | 注意: 出于安全性的考量,插件脚本中只允许使用有限的标准库函数,其中 IO 库中的所有函数,OS 库中的 `execute`, `exit`, `remove`, `setlocale`, `tmpname` 等函数均被禁用。 18 | 19 | ## 注册 20 | 21 | 每个插件都需要调用注册函数,注册函数的参数根据插件的类型有所不同。 22 | 23 | 其函数签名如下: 24 | 25 | ```lua 26 | function skynet.register(type, ...) 27 | ``` 28 | 29 | `type` 代表插件的类型,可能的取值为: 30 | - `skynet.TYPE_PROCESS` process 插件 31 | - `skynet.TYPE_TICKER` ticker 插件 32 | 33 | ### process 插件 34 | 35 | process 类型的插件会被特定的请求触发,其注册方式如下: 36 | 37 | ```lua 38 | local skynet = require "skynet" 39 | 40 | local match = { 41 | ip = "0.0.0.0/0", -- 字符串类型,要匹配的 ip 段,使用 cidr 记法 42 | host = ".*\\.chaitin\\.cn", -- 字符串类型,要匹配的域名,支持 perl 风格的正则表达式 43 | urlpath = ".*", -- 字符串类型,访问路径,同样支持正则 44 | type = [type value], -- 指定 plugin 被触发的模式(可省略) 45 | -- skynet.MATCH_TYPE_DEFAULT:优化的触发模式,每秒钟最多一次(默认值) 46 | -- skynet.MATCH_TYPE_ALL:每个请求都被触发 47 | target = [target value], -- 指定plugin要命中的请求类型(可省略) 48 | -- skynet.MATCH_TARGET_ALL:命中全部请求(默认值) 49 | -- skynet.MATCH_TARGET_ACCESS:命中未被拦截的请求 50 | -- skynet.MATCH_TARGET_DETECT:命中产生攻击日志的请求(包括被拦截的以及未拦截但产生日志的请求) 51 | } 52 | 53 | 54 | function process(ip, host, urlpath) 55 | -- 在此处理请求 56 | end 57 | 58 | skynet.register(skynet.TYPE_PROCESS, match, process) 59 | ``` 60 | 61 | - `match` 是一个 table,用来声明插件的触发规则,其中,`ip` `host` `urlpath` 规定了能够触发插件的请求的形式。如果只关心请求中的某些数据,可以据此省略 table 中的某些数据域(field)。比如只关心请求中的 IP,可以在 `match` 中仅指定 IP。 62 | ``` 63 | 注意: 域的组合不是任意的,目前支持的组合有 (ip)、(ip,host)、(host,urlpath)、(ip,host,urlpath) 四种。不同的组合选择将导致不同的匹配行为。 64 | ``` 65 | 66 | 例如,如下两种组合理论上能够匹配完全相同的请求: 67 | 1. `{ip = "0.0.0.0/0", host = "localhost", urlpath = "/"}` 68 | 2. `{host = "localhost", urlpath = "/"}` 69 | 70 | 但是,这两种组合却会导致对插件的不同触发行为。第一个组合会针对不同的 ip 进行单独触发,而第二个在触发时并不关心 ip 的情况,只针对不同的 `host` 和 `urlpath` 进行触发。因此第二个组合会减少实际的触发次数,从而获得更好的性能。所以在实现 plugin 时,如果对于某个域并不关心,最好的做法是不写该域,而不是使用类似于`0.0.0.0/0`或`.*`这样的通配符。 71 | ``` 72 | 注意: 当 match 的 target = skynet.MATCH_TARGET_ALL 且用户在 SafeLine 的「入侵检测」→「防护策略管理」中添加了「自定义规则」时,插件可能会接收到类型分别为 skynet.MATCH_TARGET_ACCESS 和 skynet.MATCH_TARGET_DETECT 的重复请求,如果需要去重,请使用 skynet.get_target() 判断类型后再做处理。 73 | ``` 74 | - `process` 是插件的的回调函数,当插件触发时被调用,参数的值即为请求对应的客户端 IP 地址(`ip`)、访问域名(`host`)、访问路径(`urlpath`)。例如,对于一个访问 `https://chaitin.cn/safeline.html` 的请求,其域名是 `chaitin.cn`,访问路径是 `/safeline.html` )。 75 | ``` 76 | 注意: 77 | 为了保证性能,默认设置插件的 match.type = skynet.MATCH_TYPE_DEFAULT,同样的请求(即 ip、host、urlpath 相同)在一秒内只会被触发一次。 78 | 79 | 对于搭载了插件平台版本 >= 1.0.0 的 SafeLine(详询技术人员), 当插件的 target = skynet.MATCH_TARGET_DETECT 时,此模式不起作用,等同于 skynet.MATCH_TYPE_ALL 80 | 81 | host 和 urlpath 中的正则表达式均匹配整个字符串(插件平台在正则后插入了 $),例如 urlpath = "/safeline" 仅匹配访问 /safeline 或者 /safeline?key=xxxx 的请求,而不匹配访问 /safeline.html 的请求。 82 | ``` 83 | 84 | - 回调函数无返回值。 85 | 86 | ### ticker 插件 87 | 88 | ticker 类型的插件会被定时触发,其注册方式如下: 89 | 90 | ```lua 91 | local skynet = require "skynet" 92 | 93 | local duration = 10 94 | 95 | function ticker(dur) 96 | -- 在此处理定时任务 97 | end 98 | 99 | skynet.register(skynet.TYPE_TICKER, duration, ticker) 100 | ``` 101 | - `durtaion` 是一个整数,用来声明插件的触发间隔,其单位为秒。 102 | 103 | - `ticker` 是插件的回调函数,当插件触发时被调用,参数 `dur` 的值为插件的触发间隔,其单位为秒。 104 | 105 | - ticker 插件通常用于定时获取所有请求的统计信息,例如获取访问频率最高的某个 URL(详见本文档「接口函数」→「top 类」一节)。 106 | ``` 107 | 注意: 并非所有的接口函数都可以在 ticker 插件的回调函数中被调用,以接口函数的说明为准。 108 | ``` 109 | 110 | - 回调函数无返回值。 111 | 112 | ## 接口函数 113 | 114 | skynet 包中提供的函数以下统称为接口函数。 115 | 116 | ### key table 117 | key 是一个在接口函数中广泛使用的数据结构。 118 | 119 | ```lua 120 | key = { 121 | ip = "", 122 | host = "", 123 | urlpath = "", 124 | } 125 | ``` 126 | `key` 的结构和 process 插件的 `match` 相似,但只保留了描述请求的域,和 match 不同的是,key 中的数据只能是确定值,即,`ip` 只能是单个的 ip 地址,`host` 和 `urlpath` 不支持正则表达式。 127 | 128 | 用户可将其作为参数传递给接口函数,查询指定 `ip/host/urlpath` 的访问频率,风险级别等,或者对其进行封禁。 129 | 130 | 以下函数中的 `key` 参数为上述的 `key table`,代表符合条件的一类请求。 131 | 132 | ### stat 类 133 | 134 | 获取(非攻击)请求的统计信息,理论上可查询的最大时间范围为 60 分钟 + 60 秒。 135 | 136 | `function skynet.stat_visit(key, time) `: 137 | - 返回在过去 `time` 秒内符合 `key` 约束的请求数。 138 | - `time` 为整型,以秒为单位。 139 | - 返回值为整型。 140 | 141 | `function skynet.stat_time(key, time) `: 142 | - 返回在过去 time 秒内符合 key 约束的请求消耗服务器的时间。 143 | - `time` 为整型,以秒为单位。 144 | - 返回值为整型,以秒为单位。 145 | 146 | `function skynet.stat_resp_code(key, time)` : 147 | - 返回一个以 HTTP 状态码为索引的 table,其值为在过去 time 秒内符合 key 约束的不同 HTTP 状态码的请求数。 148 | - `time` 为整型,以秒为单位。 149 | - 返回值为 table。 150 | - skynet.RESP_CODE_1XX 请求已接受,需要继续处理 151 | - skynet.RESP_CODE_2XX 请求已成功 152 | - skynet.RESP_CODE_3XX 请求重定向 153 | - skynet.RESP_CODE_4XX 客户端错误 154 | - skynet.RESP_CODE_5XX 服务端错误 155 | 156 | ### top 类 157 | 158 | `function skynet.top_n(type, n, duration)`: 159 | - 查询在 `duration` 秒内出现频率最高的前 n 条数据,数据类型由 `type` 指定。 160 | - `type` 为整型,其可能的取值如下: 161 | - `skynet.TOP_IP` 查询指定时间段内 所有请求中出现频率最高的 **IP** 162 | - `skynet.TOP_IP_HOST` 查询指定时间段内,所有请求中出现频率最高的 **IP**,**域名** 组合 163 | - `skynet.TOP_HOST_URLPATH` 查询指定时间段内,所有请求中出现频率最高的 **域名**,**访问路径** 组合 164 | - `skynet.TOP_IP_HOST_URLPATH` 查询指定时间段内,所有请求中出现频率最高的 **IP**,**域名**,**访问路径** 的组合 165 | - `skynet.TOP_SESSION` 查询指定时间段内,所有请求中出现频率最高的 **session** 166 | - `skynet.TOP_IP_SESSION` 查询指定时间段内,所有请求中出现频率最高的 **IP**,**session**组合 167 | - `skynet.TOP_SESSION_HOST` 查询指定时间段内,所有请求中出现频率最高的的 **session**,**域名** 组合 168 | - `skynet.TOP_SESSION_HOST_URLPATH` 查询指定时间段内,所有请求中出现频率最高的的 **session**,**域名** ,**访问路径** 组合 169 | ``` 170 | 注意:此处的 session 特指 SafeLine 的「Session 策略」中所指定的 session,请在「站点管理」→「防护站点管理」中选择一个虚拟服务器,在其子站点的编辑窗口中即可指定 session 策略。 171 | ``` 172 | - `n`, `duration` 均为整型,`duration` 可能的取值如下: 173 | - skynet.TOP_DURATION_5S 查询 5 秒内的数据 174 | - 返回一个 key table 的数组,key table 中额外的 count 域保存了访问次数。 175 | ``` 176 | 注意:当插件平台的部署模式为集群时,该函数取得的数据为当前节点下的数据,在之后的版本中会支持获取全部节点合并后的数据。 177 | ``` 178 | 179 | ### action 类 180 | 181 | SafeLine 的「访问控制规则」的部分接口。 182 | 183 | `function skynet.action_ban(key, duration)`: 184 | - 封禁符合 `key` 约束的请求,有效时间为 `duration` 秒。 185 | - `duration` 为整型,以秒为单位。 186 | - 提交请求成功时函数返回 `nil`,失败时返回包含错误信息的字符串。 187 | 188 | `function skynet.action_limit(key, duration, period, limit)`: 189 | - 限制符合 `key` 约束的请求的访问频率,在 `period` 内只允许访问 `limit` 次,有效时间为 `duration` 秒。 190 | - `duration` 为整型,以秒为单位;`period` 为整型,以秒为单位;`limit` 为整型。 191 | - 提交请求成功时函数返回 `nil`,失败时返回包含错误信息的字符串。 192 | 193 | ### db 类 194 | 195 | 插件系统内置的键值对型数据库(key-value database)接口,所有接口函数的第一个参数需要指定在哪个 DB 中操作,可选值为 `skynet.DB_GLOBAL` 或 `skynet.DB_LOCAL`,LOCAL DB 对于每个插件是独立的,不同插件之间互不干扰。GLOBAL DB 是所有插件共享的,不同插件可以通过 GLOBAL DB 交换数据。 196 | 197 | key 和 value 的类型均为字符串,对于 `skynet.db_add` 以及 `skynet.db_sub` 函数,其返回值会自动转换为数字。 198 | 199 | 从性能角度考虑,如果不是必须的话,尽量使用 LOCAL DB,可以获得更高的性能。 200 | 201 | ``` 202 | function skynet.db_get(db, key) 203 | ``` 204 | ``` 205 | function skynet.db_set(db, key, val) 206 | ``` 207 | ``` 208 | function skynet.db_del(db, key) 209 | ``` 210 | ``` 211 | function skynet.db_size(db) 212 | ``` 213 | ``` 214 | function skynet.db_clear(db) 215 | ``` 216 | ``` 217 | function skynet.db_add(db, key, val) 218 | ``` 219 | 执行:`db[key] += val`,不需要事先创建,初始值为 0 220 | ``` 221 | function skynet.db_sub(db, key, val) 222 | ``` 223 | 执行:`db[key] -= val`,不需要事先创建,初始值为 0 224 | 225 | ### http 类 226 | 227 | 插件系统内置的 HTTP 客户端接口。 228 | `function skynet.http_get(url, header)`: 229 | - 对地址 `url` 发起一次 HTTP GET 请求,`url` 中可携带 GET 参数,在 `header` 中可指定 HTTP header。 230 | - `url` 为字符串类型,`header` 为 table 类型。 231 | - 返回值为 `(resp, err)`,`err` 为字符串类型,包含请求错误信息,无错误时值为 `nil`,当无错误发生时 `resp` 为 table 类型,包含 HTTP 请求的响应,其结构示例如下: 232 | ``` 233 | { 234 | status_code = 200, 235 | header = { 236 | -- ... 237 | }, 238 | body = "Response body", 239 | } 240 | ``` 241 | 242 | `function skynet.http_post(url, header, data)`: 243 | - 对地址 `url` 发起一次 HTTP POST 请求,在 `header` 中可指定 HTTP header,`data` 为 POST 数据。 244 | - `url` 为字符串类型,`header` 为 table 类型,`data` 为字符串类型。 245 | - 返回值同 `skynet.http_get()`。 246 | 247 | ### log 类 248 | 249 | `function skynet.log(tag, msg)`: 250 | - 产生一条插件日志,日志会被记录到日志文件中或者显示在 SafeLine 的界面上。 251 | - `tag` 和 `msg` 均为字符串类型,`tag` 用于标记日志的类别,值为 “system” 的 tag 被插件系统内部保留使用,`msg` 是日志内容,`tag` 和 `msg` 均不得为空。 252 | - 函数无返回值。 253 | ``` 254 | 注意:大量发送日志会给 SafeLine 后端造成压力,因此插件平台会限制日志的发送频率,超过阈值的日志会被直接丢弃。 255 | ``` 256 | 257 | ### 其他类 258 | 259 | `function skynet.get_target()`:获取当前请求的 target 信息,可能的返回值如下: 260 | - `skynet.MATCH_TARGET_ACCESS` 普通的访问请求 261 | - `skynet.MATCH_TARGET_DETECT` 被 SafeLine 命中的请求,可能是一个攻击请求 262 | 263 | ``` 264 | 注意: 该函数不适用于 ticker 插件。 265 | ``` 266 | 267 | `function skynet.get_sesion()`: 268 | - 获取当前请求的 session 信息。 269 | - 返回值为二维数组,内层数组长度固定为 2。其第一个元素为 session id,第二个元素为 session value。 270 | ``` 271 | [ 272 | ["session_id", "session_value"], 273 | ["session_id", "session_value"], 274 | ... 275 | ] 276 | ``` 277 | ``` 278 | 注意: 该函数不适用于 ticker 插件。 279 | ``` 280 | 281 | `function skynet.get_detailed_info()`: 282 | - 获取当前请求的详细信息,返回值为 table,其索引随着 target 的不同。 283 | ``` 284 | 注意: 该函数不适用于 ticker 插件。 285 | ``` 286 | - 返回值为 table。 287 | 288 | 289 | 当请求的 target 是 `skynet.MATCH_TARGET_ACCESS` 是,其内容为: 290 | ``` 291 | { 292 | ip = "", -- IP,可能是代理服务器的 IP 地址 293 | time = 0, -- 消耗后端服务器的时间,以秒为单位 294 | host = "", -- 域名 295 | uri = "", -- 访问路径 296 | src_ip = "", -- 真正的源 IP,可能为空 297 | } 298 | ``` 299 | 300 | 当请求的 target 是 `skynet.MATCH_TARGET_DETECT` 时,说明这是一个被 SafeLine 拦截的请求,其部分内容为(完整内容待补全): 301 | ``` 302 | 303 | { 304 | node = "", -- 检测服务器节点名称 305 | timestamp = 0, -- Unix 时间戳 306 | host = "", -- 域名 307 | attack_type = 1, -- 攻击类型 308 | risk_level = 0, -- 风险级别,其取值和 skynet.stat_risk() 返回的 table 的索引相同 309 | event_id = "", -- 该次拦截的事件 ID 310 | req_header_raw = "", -- 经过编码的请求的 HTTP 头 311 | rule_id = "", -- 命中该次拦截的规则 ID 312 | src_ip = "", -- 源 IP 313 | user_agent = "", -- User Agent 314 | urlpath = "", -- 访问路径 315 | action = 1, -- SafeLine 对此请求执行的动作 316 | method = "", -- 请求的方法,比如 GET/POST 等 317 | dest_ip = "", -- 请求的目的 IP 318 | resp_header_raw = "", -- 原始响应头 319 | resp_body = "", -- 原始响应内容 320 | } 321 | ``` 322 | 323 | ## 示例 324 | 一个典型的插件如下所示: 325 | ```lua 326 | local skynet = require "skynet" 327 | 328 | match = { 329 | ip = "0.0.0.0/0", 330 | host = ".+", 331 | urlpath = ".+", 332 | type = skynet.MATCH_TYPE_ALL, 333 | target = skynet.MATCH_TARGET_ALL, 334 | } 335 | 336 | function process(ip, host, urlpath) 337 | skynet.log("process", "matched") 338 | 339 | local m = { 340 | ip = ip, 341 | host = host, 342 | } 343 | 344 | for i, v in ipairs(skynet.get_session()) do 345 | skynet.log("process", v[0] .. ": " .. v[1]) 346 | end 347 | 348 | local c = skynet.stat_visit(m, 10) 349 | 350 | if c > 30 then 351 | skynet.action_ban(m, 60) 352 | end 353 | end 354 | 355 | skynet.register(skynet.TYPE_PROCESS, match, process) 356 | ``` 357 | -------------------------------------------------------------------------------- /series_10/api_doc/conf.md: -------------------------------------------------------------------------------- 1 | # /api/v1/conf/waf_state/log_ip_source 2 | 3 | ## POST 4 | 5 | 数据格式 6 | 7 | |字段名|数据类型|是否必填/默认值|NULL|其他| 8 | |:--|:-:|:-:|:-:|:-:| 9 | |log_ip_source | 列表 | 必填 | False | - 详见下方表格 | 10 | 11 | log_ip_source 12 | 13 | |字段名|数据类型|是否必填/默认值|NULL|其他| 14 | |:--|:-:|:-:|:-:|:-:| 15 | |子字段 | 字符串 | 必填 | False | 最大长度: 64; | 16 | 17 | 18 | 19 | ### 设置检测日志 ip 来源 20 | 21 | 对于 x-forwarded-for,还可以添加 :-1 :-2 等后缀表示取第几个ip 22 | 23 | 请求 24 | 25 | ```js 26 | { 27 | "log_ip_source": [ 28 | "Socket", 29 | "X-Forwarded-For", 30 | "My-Header", 31 | "X-Forwarded-For:-1" 32 | ] 33 | } 34 | ``` 35 | 36 | 响应 37 | 38 | ```js 39 | { 40 | "err": null, 41 | "msg": "", 42 | "data": { 43 | "log_ip_source": [ 44 | "Socket", 45 | "X-Forwarded-For", 46 | "My-Header", 47 | "X-Forwarded-For:-1" 48 | ] 49 | } 50 | } 51 | ``` 52 | 53 | # /api/v3/snserver/heartbeat 54 | 55 | ## POST 56 | 57 | 数据格式 58 | 59 | |字段名|数据类型|是否必填/默认值|NULL|其他| 60 | |:--|:-:|:-:|:-:|:-:| 61 | |timestamp | 浮点数字 | 必填 | False | - | 62 | |time_from | 浮点数字 | 必填 | False | - | 63 | |node | 字符串 | 必填 | False | 最大长度: 128; | 64 | |cpu | 浮点数字 | 必填 | False | - | 65 | |mem | 浮点数字 | 必填 | False | - | 66 | |req_num_total | 整型数字 | 必填 | False | - | 67 | |req_num_blocked | 整型数字 | 必填 | False | - | 68 | |req_num_accepted | 整型数字 | 必填 | False | - | 69 | |req_num_handled | 整型数字 | 必填 | False | - | 70 | |req_num_dropped | 整型数字 | 必填 | False | - | 71 | |req_average_time | 浮点数字 | 必填 | False | - | 72 | |ioread | 浮点数字 | 必填 | False | - | 73 | |iowrite | 浮点数字 | 必填 | False | - | 74 | |req_ngx_time | 整型数字 | 必填 | False | - | 75 | |req_ngx_count | 整型数字 | 必填 | False | - | 76 | |detector_version | 整型数字 | 必填 | False | - | 77 | |workers | 列表 | 必填 | False | - 详见下方表格 | 78 | |policy_version | 字典 | - | - | 详见下面表格 | 79 | 80 | workers 81 | 82 | |字段名|数据类型|是否必填/默认值|NULL|其他| 83 | |:--|:-:|:-:|:-:|:-:| 84 | |子字段 | 字典 | - | - | 详见下面表格 | 85 | 86 | 子字段 87 | 88 | |字段名|数据类型|是否必填/默认值|NULL|其他| 89 | |:--|:-:|:-:|:-:|:-:| 90 | |index | 整型数字 | 必填 | False | - | 91 | |cpu | 浮点数字 | 必填 | False | - | 92 | |ioread | 浮点数字 | 必填 | False | - | 93 | |iowrite | 浮点数字 | 必填 | False | - | 94 | 95 | 96 | policy_version 97 | 98 | |字段名|数据类型|是否必填/默认值|NULL|其他| 99 | |:--|:-:|:-:|:-:|:-:| 100 | |waf_rule_version | 整型数字 | 必填 | False | - | 101 | |cc_rule_version | 整型数字 | 必填 | False | - | 102 | 103 | 104 | 105 | ### snserver 发送 heartbeat 106 | 107 | 108 | 109 | 请求 110 | 111 | ```js 112 | { 113 | "timestamp": 1545297403.5166395, 114 | "time_from": 1545297398.5166395, 115 | "node": "node", 116 | "policy_version": { 117 | "waf_rule_version": 1, 118 | "cc_rule_version": 1 119 | }, 120 | "cpu": 0.9, 121 | "mem": 0.8, 122 | "req_num_total": 100, 123 | "req_num_blocked": 15, 124 | "req_num_accepted": 15, 125 | "req_num_handled": 15, 126 | "req_num_dropped": 10, 127 | "req_average_time": 100, 128 | "ioread": 1000, 129 | "iowrite": 200, 130 | "req_ngx_time": 1000, 131 | "req_ngx_count": 2000, 132 | "detector_version": 4, 133 | "workers": [ 134 | { 135 | "index": 1, 136 | "cpu": 0.6, 137 | "ioread": 300, 138 | "iowrite": 500 139 | } 140 | ] 141 | } 142 | ``` 143 | 144 | 响应 145 | 146 | ```js 147 | { 148 | "err": null, 149 | "msg": "", 150 | "data": null 151 | } 152 | ``` 153 | 154 | # /api/v1/stat/node/delete 155 | 156 | ## POST 157 | 158 | 数据格式 159 | 160 | |字段名|数据类型|是否必填/默认值|NULL|其他| 161 | |:--|:-:|:-:|:-:|:-:| 162 | |node | 字符串 | 必填 | False | - | 163 | 164 | 165 | 166 | ### 删除检测节点数据 167 | 168 | 169 | 170 | 请求 171 | 172 | ```js 173 | { 174 | "node": "node" 175 | } 176 | ``` 177 | 178 | 响应 179 | 180 | ```js 181 | { 182 | "err": null, 183 | "msg": "", 184 | "data": null 185 | } 186 | ``` 187 | 188 | # /api/v1/stat/node 189 | 190 | ## GET 191 | 192 | 193 | 194 | ### 获取节点状态 195 | 196 | 197 | 198 | 响应 199 | 200 | ```js 201 | { 202 | "err": null, 203 | "msg": "", 204 | "data": [ 205 | { 206 | "node": "node", 207 | "timestamp": 1545297403.2744617, 208 | "last_timestamp": 1545297398.2744617, 209 | "last_update_at": 1545297403.2821705, 210 | "cpu": 0.9, 211 | "mem": 0.8, 212 | "num_request": 20.0, 213 | "num_attack": 3.0, 214 | "num_accepted": 3.0, 215 | "num_handled": 3.0, 216 | "num_dropped": 2.0, 217 | "process_time": 100, 218 | "detector_version": 4, 219 | "ioread": 200.0, 220 | "iowrite": 40.0, 221 | "workers": [ 222 | { 223 | "index": 1, 224 | "cpu": 0.6, 225 | "ioread": 60.0, 226 | "iowrite": 100.0 227 | } 228 | ], 229 | "ngx_count": 400.0, 230 | "ngx_time": 200.0, 231 | "status": "normal", 232 | "config": "policy_update_failed" 233 | } 234 | ] 235 | } 236 | ``` 237 | 238 | # /api/v1/conf/syslog 239 | 240 | ## GET 241 | 242 | 243 | 244 | ### 获取 syslog 配置,目前 syslog 处于 未配置过的状态 245 | 246 | 247 | 248 | 响应 249 | 250 | ```js 251 | { 252 | "err": null, 253 | "msg": "", 254 | "data": { 255 | "ip": null, 256 | "port": null, 257 | "format": { 258 | "type": "json" 259 | } 260 | } 261 | } 262 | ``` 263 | 264 | ### 获取 syslog 配置 265 | 266 | 267 | 268 | 响应 269 | 270 | ```js 271 | { 272 | "err": null, 273 | "msg": "", 274 | "data": { 275 | "ip": "1.2.3.4", 276 | "port": 80, 277 | "format": { 278 | "data": [], 279 | "type": "custom" 280 | } 281 | } 282 | } 283 | ``` 284 | 285 | ## PUT 286 | 287 | 数据格式 288 | 289 | |字段名|数据类型|是否必填/默认值|NULL|其他| 290 | |:--|:-:|:-:|:-:|:-:| 291 | |ip | 字符串 | 必填 | False | - | 292 | |port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | 293 | |format | 字典 | - | - | 详见下面表格 | 294 | 295 | format 296 | 297 | |字段名|数据类型|是否必填/默认值|NULL|其他| 298 | |:--|:-:|:-:|:-:|:-:| 299 | |type | 指定选项 | 必填 | False | 选项是: ['json', 'custom'] | 300 | |data | 列表 | 非必填/无默认值 | False | - 详见下方表格 | 301 | 302 | data 303 | 304 | |字段名|数据类型|是否必填/默认值|NULL|其他| 305 | |:--|:-:|:-:|:-:|:-:| 306 | 307 | 308 | 309 | ### 修改 syslog 配置 310 | 311 | 312 | 313 | 请求 314 | 315 | ```js 316 | { 317 | "ip": "1.2.3.4", 318 | "port": 80, 319 | "format": { 320 | "type": "custom", 321 | "data": [] 322 | } 323 | } 324 | ``` 325 | 326 | 响应 327 | 328 | ```js 329 | { 330 | "err": null, 331 | "msg": "", 332 | "data": { 333 | "ip": "1.2.3.4", 334 | "port": 80, 335 | "format": { 336 | "data": [], 337 | "type": "custom" 338 | } 339 | } 340 | } 341 | ``` 342 | 343 | # /api/v1/conf/ha_status 344 | 345 | ## GET 346 | 347 | 348 | 349 | ### 获取 ha 配置 350 | 351 | 352 | 353 | 响应 354 | 355 | ```js 356 | { 357 | "err": null, 358 | "msg": "", 359 | "data": { 360 | "status": true, 361 | "mode": "master", 362 | "nopreempt": true, 363 | "interface_name": "", 364 | "downgraded": false, 365 | "vrrp_state": "master", 366 | "sync_state": "success", 367 | "state": "normal" 368 | } 369 | } 370 | ``` 371 | 372 | # /api/v1/conf/ha 373 | 374 | ## PUT 375 | 376 | 数据格式 377 | 378 | |字段名|数据类型|是否必填/默认值|NULL|其他| 379 | |:--|:-:|:-:|:-:|:-:| 380 | |interface_name | 字符串 | 必填 | False | 最大长度: 128; | 381 | |mode | 指定选项 | 必填 | False | 选项是: ['master', 'backup'] | 382 | |status | 布尔 | 必填 | False | - | 383 | |nopreempt | 布尔 | 必填 | False | - | 384 | 385 | 386 | 387 | ### ha 配置 388 | 389 | 390 | 391 | 请求 392 | 393 | ```js 394 | { 395 | "interface_name": "bonding_0", 396 | "mode": "master", 397 | "status": true, 398 | "nopreempt": true 399 | } 400 | ``` 401 | 402 | 响应 403 | 404 | ```js 405 | { 406 | "err": null, 407 | "msg": "", 408 | "data": { 409 | "interface_name": "bonding_0", 410 | "mode": "master", 411 | "status": true, 412 | "nopreempt": true 413 | } 414 | } 415 | ``` 416 | 417 | # /api/v1/conf/system_service 418 | 419 | ## GET 420 | 421 | 数据格式 422 | 423 | |字段名|数据类型|是否必填/默认值|NULL|其他| 424 | |:--|:-:|:-:|:-:|:-:| 425 | |services | 指定选项 | 必填 | False | 选项是: ['web_management', 'icmp_echo', 'ssh', 'snmp'] | 426 | 427 | 428 | 429 | ### 获取系统服务设置 430 | 431 | 432 | 433 | 请求 434 | 435 | ```js 436 | { 437 | "services": [ 438 | "ssh", 439 | "icmp_echo" 440 | ] 441 | } 442 | ``` 443 | 444 | 响应 445 | 446 | ```js 447 | { 448 | "err": null, 449 | "msg": "", 450 | "data": { 451 | "icmp_echo": [ 452 | { 453 | "interface": "br0", 454 | "interface_comment": "接口-1", 455 | "enabled": false, 456 | "port": 65535 457 | }, 458 | { 459 | "interface": "br1", 460 | "interface_comment": "接口-2", 461 | "enabled": false, 462 | "port": 65535 463 | }, 464 | { 465 | "interface": "br2", 466 | "interface_comment": "接口-3", 467 | "enabled": false, 468 | "port": 65535 469 | }, 470 | { 471 | "interface": "br3", 472 | "interface_comment": "接口-4", 473 | "enabled": false, 474 | "port": 65535 475 | }, 476 | { 477 | "interface": "br4", 478 | "interface_comment": "接口-5", 479 | "enabled": false, 480 | "port": 65535 481 | } 482 | ], 483 | "ssh": [ 484 | { 485 | "interface": "br0", 486 | "interface_comment": "接口-1", 487 | "enabled": false, 488 | "port": 65535 489 | }, 490 | { 491 | "interface": "br1", 492 | "interface_comment": "接口-2", 493 | "enabled": false, 494 | "port": 65535 495 | }, 496 | { 497 | "interface": "br2", 498 | "interface_comment": "接口-3", 499 | "enabled": false, 500 | "port": 65535 501 | }, 502 | { 503 | "interface": "br3", 504 | "interface_comment": "接口-4", 505 | "enabled": false, 506 | "port": 65535 507 | }, 508 | { 509 | "interface": "br4", 510 | "interface_comment": "接口-5", 511 | "enabled": false, 512 | "port": 65535 513 | } 514 | ] 515 | } 516 | } 517 | ``` 518 | 519 | ## PUT 520 | 521 | 数据格式 522 | 523 | |字段名|数据类型|是否必填/默认值|NULL|其他| 524 | |:--|:-:|:-:|:-:|:-:| 525 | |type | 指定选项 | 必填 | False | 选项是: ['web_management', 'icmp_echo', 'ssh', 'snmp'] | 526 | |interface | 字符串 | 必填 | False | 最大长度: 128; | 527 | |enabled | 布尔 | 必填 | False | - | 528 | |port | 整型数字 | 非必填/0 | False | 最小值: 1; 最大值: 65535 | 529 | 530 | 531 | 532 | ### 设置系统服务,其中 icmp_echo 不需要端口 533 | 534 | 535 | 536 | 请求 537 | 538 | ```js 539 | { 540 | "interface": "br0", 541 | "type": "ssh", 542 | "enabled": true, 543 | "port": 22 544 | } 545 | ``` 546 | 547 | 响应 548 | 549 | ```js 550 | { 551 | "err": null, 552 | "msg": "", 553 | "data": null 554 | } 555 | ``` 556 | 557 | -------------------------------------------------------------------------------- /series_10/api_doc/metrics.md: -------------------------------------------------------------------------------- 1 | # /api/v1/log/delete 2 | 3 | ## POST 4 | 5 | 数据格式 6 | 7 | |字段名|数据类型|是否必填/默认值|NULL|其他| 8 | |:--|:-:|:-:|:-:|:-:| 9 | |domain | 字符串 | 非必填/无默认值 | False | - | 10 | |time_after | 浮点数字 | 非必填/无默认值 | False | - | 11 | |time_before | 浮点数字 | 非必填/无默认值 | False | - | 12 | |risk_level | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'low', 'medium', 'high'] | 13 | |count | 整型数字 | 非必填/无默认值 | False | - | 14 | |offset | 整型数字 | 非必填/无默认值 | False | - | 15 | |src_ip | 字符串 | 非必填/无默认值 | False | - | 16 | |attack_type | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'sql_injection', 'xss', 'csrf', 'ssrf', 'dos', 'backdoor', 'deserialization', 'code_execution', 'code_injection', 'command_injection', 'file_upload', 'file_inclusion', 'redirect', 'weak_permission', 'info_leak', 'unauthorized_access', 'unsafe_config', 'xxe', 'xpath_injection', 'ldap_injection', 'directory_traversal', 'scanner', 'permission_bypass', 'acl_bypass', 'file_write', 'file_download', 'file_deletion', 'logic_error', 'crlf_injection', 'ssti', 'click_hijacking', 'buffer_overflow', 'integer_overflow', 'format_string', 'race_condition', 'timeout', 'unknown'] | 17 | |action | 指定选项 | 非必填/无默认值 | False | 选项是: ['allow', 'deny'] | 18 | |event_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | 19 | |rule_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | 20 | 21 | 22 | 23 | ### 删除防护日志 24 | 25 | 26 | 27 | 请求 28 | 29 | ```js 30 | { 31 | "risk_level": [ 32 | "high", 33 | "low" 34 | ], 35 | "attack_type": [ 36 | "sql_injection", 37 | "xss" 38 | ], 39 | "action": [ 40 | "deny", 41 | "allow" 42 | ] 43 | } 44 | ``` 45 | 46 | 响应 47 | 48 | ```js 49 | { 50 | "err": null, 51 | "msg": "", 52 | "data": {} 53 | } 54 | ``` 55 | 56 | ### 删除单条日志 57 | 58 | 59 | 60 | 请求 61 | 62 | ```js 63 | { 64 | "event_id": "064d9798c40f41bcb7cd7c7aec008ade" 65 | } 66 | ``` 67 | 68 | 响应 69 | 70 | ```js 71 | { 72 | "err": null, 73 | "msg": "", 74 | "data": {} 75 | } 76 | ``` 77 | 78 | # /api/v1/log/detail 79 | 80 | ## GET 81 | 82 | 83 | 84 | ### 获取攻击日志详情 85 | 86 | 87 | 88 | 请求 89 | 90 | ```js 91 | { 92 | "id": 0 93 | } 94 | ``` 95 | 96 | 响应 97 | 98 | ```js 99 | { 100 | "err": null, 101 | "msg": "", 102 | "data": { 103 | "id": 0, 104 | "timestamp": 1545297166.357962, 105 | "node": "node", 106 | "province": "北京", 107 | "src_ip": "66.161.242.142", 108 | "src_port": 80, 109 | "dest_ip": "192.168.2.1", 110 | "dest_port": 80, 111 | "payload": "payload", 112 | "location": "location", 113 | "event_id": "064d9798c40f41bcb7cd7c7aec008ade", 114 | "selector_id": "100", 115 | "attack_type": "xss", 116 | "risk_level": "high", 117 | "action": "deny", 118 | "method": "GET", 119 | "host": "test.com", 120 | "urlpath": "/test", 121 | "decode_path": "decode_path", 122 | "session": { 123 | "session": { 124 | "session": "test" 125 | } 126 | }, 127 | "req_header_raw": "req_header_raw", 128 | "req_body": "req_body", 129 | "resp_header_raw": "resp_header_raw", 130 | "resp_body": "resp_body", 131 | "module": "", 132 | "reason": "发现 XSS 攻击" 133 | } 134 | } 135 | ``` 136 | 137 | # /api/v1/log 138 | 139 | ## GET 140 | 141 | 数据格式 142 | 143 | |字段名|数据类型|是否必填/默认值|NULL|其他| 144 | |:--|:-:|:-:|:-:|:-:| 145 | |domain | 字符串 | 非必填/无默认值 | False | - | 146 | |time_after | 浮点数字 | 非必填/无默认值 | False | - | 147 | |time_before | 浮点数字 | 非必填/无默认值 | False | - | 148 | |risk_level | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'low', 'medium', 'high'] | 149 | |count | 整型数字 | 非必填/无默认值 | False | - | 150 | |offset | 整型数字 | 非必填/无默认值 | False | - | 151 | |src_ip | 字符串 | 非必填/无默认值 | False | - | 152 | |attack_type | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'sql_injection', 'xss', 'csrf', 'ssrf', 'dos', 'backdoor', 'deserialization', 'code_execution', 'code_injection', 'command_injection', 'file_upload', 'file_inclusion', 'redirect', 'weak_permission', 'info_leak', 'unauthorized_access', 'unsafe_config', 'xxe', 'xpath_injection', 'ldap_injection', 'directory_traversal', 'scanner', 'permission_bypass', 'acl_bypass', 'file_write', 'file_download', 'file_deletion', 'logic_error', 'crlf_injection', 'ssti', 'click_hijacking', 'buffer_overflow', 'integer_overflow', 'format_string', 'race_condition', 'timeout', 'unknown'] | 153 | |action | 指定选项 | 非必填/无默认值 | False | 选项是: ['allow', 'deny'] | 154 | |event_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | 155 | |rule_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | 156 | 157 | 158 | 159 | ### 获取日志列表 160 | 161 | 162 | 163 | 响应 164 | 165 | ```js 166 | { 167 | "err": null, 168 | "msg": "", 169 | "data": { 170 | "total": 3, 171 | "items": [ 172 | { 173 | "id": 0, 174 | "timestamp": 1545297166.616753, 175 | "node": "node", 176 | "province": "北京", 177 | "src_ip": "124.89.111.235", 178 | "src_port": 80, 179 | "dest_ip": "192.168.2.1", 180 | "dest_port": 80, 181 | "payload": "payload", 182 | "location": "location", 183 | "event_id": "064d9798c40f41bcb7cd7c7aec008ade", 184 | "selector_id": "100", 185 | "attack_type": "xss", 186 | "risk_level": "high", 187 | "action": "deny", 188 | "method": "GET", 189 | "host": "test.com", 190 | "urlpath": "/test", 191 | "decode_path": "decode_path", 192 | "session": { 193 | "session": { 194 | "session": "test" 195 | } 196 | }, 197 | "req_header_raw": "req_header_raw", 198 | "req_body": "req_body", 199 | "resp_header_raw": "resp_header_raw", 200 | "resp_body": "resp_body", 201 | "module": "", 202 | "reason": "发现 XSS 攻击" 203 | }, 204 | { 205 | "id": 1, 206 | "timestamp": 1545210766.737222, 207 | "node": "node", 208 | "province": "北京", 209 | "src_ip": "249.135.0.145", 210 | "src_port": 80, 211 | "dest_ip": "192.168.2.1", 212 | "dest_port": 80, 213 | "payload": "payload", 214 | "location": "location", 215 | "event_id": "064d9798c40f41bcb7cd7c7aec008ade", 216 | "selector_id": "100", 217 | "attack_type": "xss", 218 | "risk_level": "high", 219 | "action": "deny", 220 | "method": "GET", 221 | "host": "test.com", 222 | "urlpath": "/test", 223 | "decode_path": "decode_path", 224 | "session": { 225 | "session": { 226 | "session": "test" 227 | } 228 | }, 229 | "req_header_raw": "req_header_raw", 230 | "req_body": "req_body", 231 | "resp_header_raw": "resp_header_raw", 232 | "resp_body": "resp_body", 233 | "module": "", 234 | "reason": "发现 XSS 攻击" 235 | }, 236 | { 237 | "id": 2, 238 | "timestamp": 1545124366.738601, 239 | "node": "node", 240 | "province": "北京", 241 | "src_ip": "82.39.218.183", 242 | "src_port": 80, 243 | "dest_ip": "192.168.2.1", 244 | "dest_port": 80, 245 | "payload": "payload", 246 | "location": "location", 247 | "event_id": "064d9798c40f41bcb7cd7c7aec008ade", 248 | "selector_id": "100", 249 | "attack_type": "xss", 250 | "risk_level": "high", 251 | "action": "deny", 252 | "method": "GET", 253 | "host": "test.com", 254 | "urlpath": "/test", 255 | "decode_path": "decode_path", 256 | "session": { 257 | "session": { 258 | "session": "test" 259 | } 260 | }, 261 | "req_header_raw": "req_header_raw", 262 | "req_body": "req_body", 263 | "resp_header_raw": "resp_header_raw", 264 | "resp_body": "resp_body", 265 | "module": "", 266 | "reason": "发现 XSS 攻击" 267 | } 268 | ] 269 | } 270 | } 271 | ``` 272 | 273 | # /api/v1/stat/attack_number_trend 274 | 275 | ## GET 276 | 277 | 数据格式 278 | 279 | |字段名|数据类型|是否必填/默认值|NULL|其他| 280 | |:--|:-:|:-:|:-:|:-:| 281 | |time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | 282 | |time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | 283 | |domain | 字符串 | 非必填/无默认值 | False | - | 284 | 285 | 286 | 287 | ### 获取攻击数时间趋势 288 | 289 | 290 | 291 | 响应 292 | 293 | ```js 294 | { 295 | "err": null, 296 | "msg": "", 297 | "data": [ 298 | { 299 | "time": 1545235200, 300 | "count": 1 301 | } 302 | ] 303 | } 304 | ``` 305 | 306 | # /api/v1/stat/req_number_trend 307 | 308 | ## GET 309 | 310 | 数据格式 311 | 312 | |字段名|数据类型|是否必填/默认值|NULL|其他| 313 | |:--|:-:|:-:|:-:|:-:| 314 | |time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | 315 | |time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | 316 | 317 | 318 | 319 | ### 获取请求数时间趋势 320 | 321 | 322 | 323 | 响应 324 | 325 | ```js 326 | { 327 | "err": null, 328 | "msg": "", 329 | "data": [ 330 | { 331 | "time": 1545235200, 332 | "count": 100 333 | } 334 | ] 335 | } 336 | ``` 337 | 338 | # /api/v1/stat/risk_level 339 | 340 | ## GET 341 | 342 | 数据格式 343 | 344 | |字段名|数据类型|是否必填/默认值|NULL|其他| 345 | |:--|:-:|:-:|:-:|:-:| 346 | |time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | 347 | |time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | 348 | |domain | 字符串 | 非必填/无默认值 | False | - | 349 | 350 | 351 | 352 | ### 获取攻击等级数据 353 | 354 | 355 | 356 | 响应 357 | 358 | ```js 359 | { 360 | "err": null, 361 | "msg": "", 362 | "data": [ 363 | { 364 | "risk_level": "low", 365 | "count": 1 366 | } 367 | ] 368 | } 369 | ``` 370 | 371 | # /api/v1/log/archive/period 372 | 373 | ## GET 374 | 375 | 376 | 377 | ### 获取归档时间 378 | 379 | 380 | 381 | 响应 382 | 383 | ```js 384 | { 385 | "err": null, 386 | "msg": "", 387 | "data": { 388 | "length": 6, 389 | "date": "month" 390 | } 391 | } 392 | ``` 393 | 394 | ## PUT 395 | 396 | 数据格式 397 | 398 | |字段名|数据类型|是否必填/默认值|NULL|其他| 399 | |:--|:-:|:-:|:-:|:-:| 400 | |date | 指定选项 | 必填 | False | 选项是: ['month', 'week', 'day'] | 401 | |length | 整型数字 | 必填 | False | 最小值: 1; | 402 | 403 | 404 | 405 | ### 获取归档历史纪录 406 | 407 | 408 | 409 | 请求 410 | 411 | ```js 412 | { 413 | "date": "month", 414 | "length": 5 415 | } 416 | ``` 417 | 418 | 响应 419 | 420 | ```js 421 | { 422 | "err": null, 423 | "msg": "", 424 | "data": { 425 | "length": 5, 426 | "date": "month" 427 | } 428 | } 429 | ``` 430 | 431 | ### 修改归档时间 432 | 433 | 434 | 435 | 请求 436 | 437 | ```js 438 | { 439 | "date": "month", 440 | "length": 1 441 | } 442 | ``` 443 | 444 | 响应 445 | 446 | ```js 447 | { 448 | "err": null, 449 | "msg": "", 450 | "data": { 451 | "length": 1, 452 | "date": "month" 453 | } 454 | } 455 | ``` 456 | 457 | -------------------------------------------------------------------------------- /series_20/plugin_demo/封禁特定地区|基于公开情报进行封禁/plugin.lua: -------------------------------------------------------------------------------- 1 | local safeline = require "safeline" 2 | 3 | -- ipdata.co API 地址 4 | local url = "https://api.ipdata.co/%s?api-key=%s" 5 | 6 | -- api key 7 | local api_key = "****" -- 替换为自己的 key 8 | 9 | -- 封禁时间 10 | local bantime = 60 11 | 12 | -- 拦截地区,字段可省略 13 | local blocks = { 14 | {country_code = "US"}, -- 拦截美国的请求 15 | {country_code = "CN", region = "zhejiang", city = "hangzhou"}, -- 浙江省杭州市,拼音 16 | {country_code = "CN", region = "sichuan"}, -- 四川省,拼音 17 | {country_code = "CN", region = "beijing"} -- 北京直辖市,拼音 18 | } 19 | 20 | -- 按威胁情报拦截,表项为 true 表示封禁该类型的 ip 21 | local block_by_threat = { 22 | is_threat = true, -- 威胁 ip 23 | is_anonymous = true, -- 匿名访问者 24 | is_bogon = true, -- 伪装身份 25 | is_abuser = true, -- 刷子(刷分、羊毛) 26 | is_proxy = true, -- 代理节点 27 | is_tor = true, -- TOR 节点 28 | is_known_attacker = true -- 已知攻击者 29 | } 30 | 31 | match = { 32 | ip = "0.0.0.0/0", 33 | host = ".+", 34 | urlpath = ".+", 35 | type = safeline.MATCH_TYPE_ALL 36 | } 37 | 38 | function make_region_key(t) 39 | local rk = "" 40 | local country = t["country_code"] 41 | if country ~= nil then 42 | rk = rk .. country 43 | else 44 | rk = rk .. "CN" 45 | end 46 | local region = t["region"] 47 | if region ~= nil and string.len(region) > 0 then 48 | rk = rk .. "_" .. region 49 | local city = t["city"] 50 | if city ~= nil and string.len(city) > 0 then 51 | rk = rk .. "_" .. city 52 | end 53 | end 54 | return string.lower(rk) 55 | end 56 | 57 | local block_region_keys = {} 58 | for i, v in ipairs(blocks) do table.insert(block_region_keys, make_region_key(v)) end 59 | 60 | function dump(o) 61 | if type(o) == 'table' then 62 | local s = '{ ' 63 | for k, v in pairs(o) do 64 | if type(k) ~= 'number' then k = '"' .. k .. '"' end 65 | s = s .. k .. ':' .. dump(v) .. ',' 66 | end 67 | s = s:sub(1, -2) 68 | return s .. '} ' 69 | else 70 | if type(o) == 'number' then 71 | return tostring(o) 72 | else 73 | return '"' .. tostring(o) .. '"' 74 | end 75 | end 76 | end 77 | 78 | --[[ json.lua 79 | A compact pure-Lua JSON library. 80 | The main functions are: json.stringify, json.parse. 81 | ## json.stringify: 82 | This expects the following to be true of any tables being encoded: 83 | * They only have string or number keys. Number keys must be represented as 84 | strings in json; this is part of the json spec. 85 | * They are not recursive. Such a structure cannot be specified in json. 86 | A Lua table is considered to be an array if and only if its set of keys is a 87 | consecutive sequence of positive integers starting at 1. Arrays are encoded like 88 | so: `[2, 3, false, "hi"]`. Any other type of Lua table is encoded as a json 89 | object, encoded like so: `{"key1": 2, "key2": false}`. 90 | Because the Lua nil value cannot be a key, and as a table value is considerd 91 | equivalent to a missing key, there is no way to express the json "null" value in 92 | a Lua table. The only way this will output "null" is if your entire input obj is 93 | nil itself. 94 | An empty Lua table, {}, could be considered either a json object or array - 95 | it's an ambiguous edge case. We choose to treat this as an object as it is the 96 | more general type. 97 | To be clear, none of the above considerations is a limitation of this code. 98 | Rather, it is what we get when we completely observe the json specification for 99 | as arbitrary a Lua object as json is capable of expressing. 100 | ## json.parse: 101 | This function parses json, with the exception that it does not pay attention to 102 | \u-escaped unicode code points in strings. 103 | It is difficult for Lua to return null as a value. In order to prevent the loss 104 | of keys with a null value in a json string, this function uses the one-off 105 | table value json.null (which is just an empty table) to indicate null values. 106 | This way you can check if a value is null with the conditional 107 | `val == json.null`. 108 | If you have control over the data and are using Lua, I would recommend just 109 | avoiding null values in your data to begin with. 110 | --]] 111 | 112 | local json = {} 113 | 114 | -- Internal functions. 115 | 116 | local function kind_of(obj) 117 | if type(obj) ~= 'table' then return type(obj) end 118 | local i = 1 119 | for _ in pairs(obj) do 120 | if obj[i] ~= nil then 121 | i = i + 1 122 | else 123 | return 'table' 124 | end 125 | end 126 | if i == 1 then 127 | return 'table' 128 | else 129 | return 'array' 130 | end 131 | end 132 | 133 | local function escape_str(s) 134 | local in_char = {'\\', '"', '/', '\b', '\f', '\n', '\r', '\t'} 135 | local out_char = {'\\', '"', '/', 'b', 'f', 'n', 'r', 't'} 136 | for i, c in ipairs(in_char) do s = s:gsub(c, '\\' .. out_char[i]) end 137 | return s 138 | end 139 | 140 | -- Returns pos, did_find; there are two cases: 141 | -- 1. Delimiter found: pos = pos after leading space + delim; did_find = true. 142 | -- 2. Delimiter not found: pos = pos after leading space; did_find = false. 143 | -- This throws an error if err_if_missing is true and the delim is not found. 144 | local function skip_delim(str, pos, delim, err_if_missing) 145 | pos = pos + #str:match('^%s*', pos) 146 | if str:sub(pos, pos) ~= delim then 147 | if err_if_missing then 148 | safeline.log("test", 149 | 'Expected ' .. delim .. ' near position ' .. pos) 150 | end 151 | return pos, false 152 | end 153 | return pos + 1, true 154 | end 155 | 156 | -- Expects the given pos to be the first character after the opening quote. 157 | -- Returns val, pos; the returned pos is after the closing quote character. 158 | local function parse_str_val(str, pos, val) 159 | val = val or '' 160 | local early_end_error = 'End of input found while parsing string.' 161 | if pos > #str then safeline.log("test", early_end_error) end 162 | local c = str:sub(pos, pos) 163 | if c == '"' then return val, pos + 1 end 164 | if c ~= '\\' then return parse_str_val(str, pos + 1, val .. c) end 165 | -- We must have a \ character. 166 | local esc_map = {b = '\b', f = '\f', n = '\n', r = '\r', t = '\t'} 167 | local nextc = str:sub(pos + 1, pos + 1) 168 | if not nextc then safeline.log("test", early_end_error) end 169 | return parse_str_val(str, pos + 2, val .. (esc_map[nextc] or nextc)) 170 | end 171 | 172 | -- Returns val, pos; the returned pos is after the number's final character. 173 | local function parse_num_val(str, pos) 174 | local num_str = str:match('^-?%d+%.?%d*[eE]?[+-]?%d*', pos) 175 | local val = tonumber(num_str) 176 | if not val then 177 | safeline.log("test", 'Error parsing number at position ' .. pos .. '.') 178 | end 179 | return val, pos + #num_str 180 | end 181 | 182 | -- Public values and functions. 183 | 184 | function json.stringify(obj, as_key) 185 | local s = {} -- We'll build the string as an array of strings to be concatenated. 186 | local kind = kind_of(obj) -- This is 'array' if it's an array or type(obj) otherwise. 187 | if kind == 'array' then 188 | if as_key then 189 | safeline.log("test", 'Can\'t encode array as key.') 190 | end 191 | s[#s + 1] = '[' 192 | for i, val in ipairs(obj) do 193 | if i > 1 then s[#s + 1] = ', ' end 194 | s[#s + 1] = json.stringify(val) 195 | end 196 | s[#s + 1] = ']' 197 | elseif kind == 'table' then 198 | if as_key then 199 | safeline.log("test", 'Can\'t encode table as key.') 200 | end 201 | s[#s + 1] = '{' 202 | for k, v in pairs(obj) do 203 | if #s > 1 then s[#s + 1] = ', ' end 204 | s[#s + 1] = json.stringify(k, true) 205 | s[#s + 1] = ':' 206 | s[#s + 1] = json.stringify(v) 207 | end 208 | s[#s + 1] = '}' 209 | elseif kind == 'string' then 210 | return '"' .. escape_str(obj) .. '"' 211 | elseif kind == 'number' then 212 | if as_key then return '"' .. tostring(obj) .. '"' end 213 | return tostring(obj) 214 | elseif kind == 'boolean' then 215 | return tostring(obj) 216 | elseif kind == 'nil' then 217 | return 'null' 218 | else 219 | error('Unjsonifiable type: ' .. kind .. '.') 220 | end 221 | return table.concat(s) 222 | end 223 | 224 | json.null = {} -- This is a one-off table to represent the null value. 225 | 226 | function json.parse(str, pos, end_delim) 227 | pos = pos or 1 228 | if pos > #str then 229 | safeline.log("test", 'Reached unexpected end of input.') 230 | end 231 | local pos = pos + #str:match('^%s*', pos) -- Skip whitespace. 232 | local first = str:sub(pos, pos) 233 | if first == '{' then -- Parse an object. 234 | local obj, key, delim_found = {}, true, true 235 | pos = pos + 1 236 | while true do 237 | key, pos = json.parse(str, pos, '}') 238 | if key == nil then return obj, pos end 239 | if not delim_found then 240 | safeline.log("test", 'Comma missing between object items.') 241 | end 242 | pos = skip_delim(str, pos, ':', true) -- true -> error if missing. 243 | obj[key], pos = json.parse(str, pos) 244 | pos, delim_found = skip_delim(str, pos, ',') 245 | end 246 | elseif first == '[' then -- Parse an array. 247 | local arr, val, delim_found = {}, true, true 248 | pos = pos + 1 249 | while true do 250 | val, pos = json.parse(str, pos, ']') 251 | if val == nil then return arr, pos end 252 | if not delim_found then 253 | safeline.log("test", 'Comma missing between array items.') 254 | end 255 | arr[#arr + 1] = val 256 | pos, delim_found = skip_delim(str, pos, ',') 257 | end 258 | elseif first == '"' then -- Parse a string. 259 | return parse_str_val(str, pos + 1) 260 | elseif first == '-' or first:match('%d') then -- Parse a number. 261 | return parse_num_val(str, pos) 262 | elseif first == end_delim then -- End of an object or array. 263 | return nil, pos + 1 264 | else -- Parse true, false, or null. 265 | local literals = { 266 | ['true'] = true, 267 | ['false'] = false, 268 | ['null'] = json.null 269 | } 270 | for lit_str, lit_val in pairs(literals) do 271 | local lit_end = pos + #lit_str - 1 272 | if str:sub(pos, lit_end) == lit_str then 273 | return lit_val, lit_end + 1 274 | end 275 | end 276 | local pos_info_str = 'position ' .. pos .. ': ' .. 277 | str:sub(pos, pos + 10) 278 | safeline.log("test", 'Invalid json syntax starting at ' .. pos_info_str) 279 | end 280 | end 281 | 282 | function banbanban(ip, body) 283 | local banip = {ip = ip} 284 | local num = string.len(body) 285 | local content = json.parse(body, 1, "}") 286 | 287 | -- 检查当前 ip 是否在封禁区域 288 | local max_depth = 1 289 | local matching = {string.lower(content["country_code"])} 290 | if type(content["region"]) == "string" then 291 | table.insert(matching, string.lower(content["region"])) 292 | max_depth = max_depth + 1 293 | end 294 | if type(content["city"]) == "string" then 295 | table.insert(matching, string.lower(content["city"])) 296 | max_depth = max_depth + 1 297 | end 298 | 299 | -- safeline.log("[debug]", dump(matching)) 300 | for i, v in ipairs(block_region_keys) do 301 | local match_depth = 1 302 | local do_ban = false 303 | for k in string.gmatch(v, "[^_]+") do 304 | if match_depth > max_depth then break end 305 | if matching[match_depth] == k then 306 | do_ban = true 307 | else 308 | do_ban = false 309 | break 310 | end 311 | match_depth = match_depth + 1 312 | end 313 | if do_ban then 314 | safeline.action_ban(safeline.ACTION_SCOPE_ALL, banip, bantime) 315 | safeline.log("区域封禁", body) 316 | break 317 | end 318 | end 319 | 320 | -- 检查当前 ip 是否命中威胁情报拦截规则 321 | for k, v in pairs(block_by_threat) do 322 | if v == true then 323 | if content["threat"][k] == "true" or content["threat"][k] == true then 324 | safeline.action_ban(safeline.ACTION_SCOPE_ALL, banip, bantime) 325 | safeline.log("威胁情报封禁", body) 326 | break 327 | end 328 | end 329 | end 330 | end 331 | 332 | function process(ip, host, urlpath) 333 | urltmp = string.format(url, ip, api_key) 334 | local resp, err = safeline.http_get(urltmp, {}) 335 | if err ~= nil and string.len(err) > 0 then 336 | safeline.log("[http]", dump(err)) 337 | else 338 | banbanban(ip, resp["body"]) 339 | end 340 | end 341 | 342 | safeline.register(safeline.TYPE_PROCESS, match, process) 343 | -------------------------------------------------------------------------------- /series_10/api_doc/account.md: -------------------------------------------------------------------------------- 1 | # /api/v1/user/cert 2 | 3 | ## GET 4 | 5 | 6 | 7 | ### 使用 code 下载证书 8 | 9 | 10 | 11 | 请求 12 | 13 | ```js 14 | { 15 | "code": "Z3yjmJlNl5pYYkwO6p" 16 | } 17 | ``` 18 | 19 | 响应 20 | 21 | ```js 22 | "不支持展示的数据类型,如文件下载等" 23 | ``` 24 | 25 | ### code 不存在 26 | 27 | 28 | 29 | 请求 30 | 31 | ```js 32 | { 33 | "code": "aaaaaaaaaaaaaaaaaa" 34 | } 35 | ``` 36 | 37 | 响应 38 | 39 | ```js 40 | { 41 | "err": "error", 42 | "msg": "证书不存在" 43 | } 44 | ``` 45 | 46 | ## POST 47 | 48 | 数据格式 49 | 50 | |字段名|数据类型|是否必填/默认值|NULL|其他| 51 | |:--|:-:|:-:|:-:|:-:| 52 | |username | 字符串 | 必填 | False | 最大长度: 32; | 53 | 54 | 55 | 56 | ### 生成证书 57 | 58 | 59 | 60 | 请求 61 | 62 | ```js 63 | { 64 | "username": "username" 65 | } 66 | ``` 67 | 68 | 响应 69 | 70 | ```js 71 | { 72 | "err": null, 73 | "msg": "", 74 | "data": { 75 | "code": "ungqMuvOhCNIIyjOfs", 76 | "password": "569ec53c" 77 | } 78 | } 79 | ``` 80 | 81 | # /api/v1/user/unlock 82 | 83 | ## POST 84 | 85 | 数据格式 86 | 87 | |字段名|数据类型|是否必填/默认值|NULL|其他| 88 | |:--|:-:|:-:|:-:|:-:| 89 | |username | 字符串 | 必填 | False | 最大长度: 32; | 90 | 91 | 92 | 93 | ### 解锁用户 94 | 95 | 96 | 97 | 请求 98 | 99 | ```js 100 | { 101 | "username": "username" 102 | } 103 | ``` 104 | 105 | 响应 106 | 107 | ```js 108 | { 109 | "err": null, 110 | "msg": "", 111 | "data": { 112 | "id": 26, 113 | "create_time": 1545297375.409247, 114 | "is_locked": false, 115 | "username": "username", 116 | "permissions": [ 117 | "log_mgmt" 118 | ], 119 | "is_deleted": false, 120 | "ip_policy": [ 121 | "*.*.*.*" 122 | ], 123 | "ip_policy_mode": "allow", 124 | "last_change_password": null, 125 | "user_experience_improvement_program": true, 126 | "login_method": "password", 127 | "session_timeout": 172800, 128 | "user_type": "normal" 129 | } 130 | } 131 | ``` 132 | 133 | # /api/v1/login 134 | 135 | ## POST 136 | 137 | 数据格式 138 | 139 | |字段名|数据类型|是否必填/默认值|NULL|其他| 140 | |:--|:-:|:-:|:-:|:-:| 141 | |username | 字符串 | 必填 | False | 最大长度: 32; | 142 | |password | 字符串 | 必填 | False | 最大长度: 32; | 143 | 144 | 145 | 146 | ### 用户登录 147 | 148 | 149 | 150 | 请求 151 | 152 | ```js 153 | { 154 | "username": "admin", 155 | "password": "admin" 156 | } 157 | ``` 158 | 159 | 响应 160 | 161 | ```js 162 | { 163 | "err": null, 164 | "msg": "", 165 | "data": { 166 | "id": 81, 167 | "create_time": 1545297379.553428, 168 | "is_locked": false, 169 | "username": "admin", 170 | "permissions": [ 171 | "log_mgmt" 172 | ], 173 | "is_deleted": false, 174 | "ip_policy": [ 175 | "*.*.*.*" 176 | ], 177 | "ip_policy_mode": "allow", 178 | "last_change_password": null, 179 | "user_experience_improvement_program": true, 180 | "login_method": "password", 181 | "session_timeout": 172800, 182 | "user_type": "normal", 183 | "need_change_password": true 184 | } 185 | } 186 | ``` 187 | 188 | ### 登录 IP 不在允许范围内 189 | 190 | 191 | 192 | 请求 193 | 194 | ```js 195 | { 196 | "username": "admin", 197 | "password": "admin" 198 | } 199 | ``` 200 | 201 | 响应 202 | 203 | ```js 204 | { 205 | "err": "ip-not-allowed", 206 | "msg": "IP不在允许范围内" 207 | } 208 | ``` 209 | 210 | ### 该用户只能使用证书登录 211 | 212 | 213 | 214 | 请求 215 | 216 | ```js 217 | { 218 | "username": "admin", 219 | "password": "admin" 220 | } 221 | ``` 222 | 223 | 响应 224 | 225 | ```js 226 | { 227 | "err": "login-failed", 228 | "msg": "该用户只能使用证书登录" 229 | } 230 | ``` 231 | 232 | ### 用户名或密码错误 233 | 234 | 235 | 236 | 请求 237 | 238 | ```js 239 | { 240 | "username": "admin", 241 | "password": "adminxxx" 242 | } 243 | ``` 244 | 245 | 响应 246 | 247 | ```js 248 | { 249 | "err": "login-failed", 250 | "msg": "用户名或密码错误" 251 | } 252 | ``` 253 | 254 | # /api/v1/logout 255 | 256 | ## POST 257 | 258 | 259 | 260 | ### 退出登录 261 | 262 | 263 | 264 | 请求 265 | 266 | ```js 267 | { 268 | "action": "logout" 269 | } 270 | ``` 271 | 272 | 响应 273 | 274 | ```js 275 | { 276 | "err": null, 277 | "msg": "", 278 | "data": null 279 | } 280 | ``` 281 | 282 | # /api/v1/user 283 | 284 | ## GET 285 | 286 | 287 | 288 | ### 使用用户名查询用户信息 289 | 290 | 291 | 292 | 请求 293 | 294 | ```js 295 | { 296 | "username": "test_admin" 297 | } 298 | ``` 299 | 300 | 响应 301 | 302 | ```js 303 | { 304 | "err": null, 305 | "msg": "", 306 | "data": { 307 | "id": 44, 308 | "create_time": 1545297376.80086, 309 | "is_locked": false, 310 | "username": "test_admin", 311 | "permissions": [ 312 | "log_mgmt", 313 | "user_mgmt", 314 | "waf_mgmt" 315 | ], 316 | "is_deleted": false, 317 | "ip_policy": [ 318 | "*.*.*.*" 319 | ], 320 | "ip_policy_mode": "allow", 321 | "last_change_password": null, 322 | "user_experience_improvement_program": true, 323 | "login_method": "password", 324 | "session_timeout": 172800, 325 | "user_type": "normal" 326 | } 327 | } 328 | ``` 329 | 330 | ### 获取用户列表 331 | 332 | 333 | 334 | 本 API 支持翻页参数 335 | 336 | 响应 337 | 338 | ```js 339 | { 340 | "err": null, 341 | "msg": "", 342 | "data": [ 343 | { 344 | "id": 45, 345 | "create_time": 1545297376.872861, 346 | "is_locked": false, 347 | "username": "test_admin", 348 | "permissions": [ 349 | "log_mgmt", 350 | "user_mgmt", 351 | "waf_mgmt" 352 | ], 353 | "is_deleted": false, 354 | "ip_policy": [ 355 | "*.*.*.*" 356 | ], 357 | "ip_policy_mode": "allow", 358 | "last_change_password": null, 359 | "user_experience_improvement_program": true, 360 | "login_method": "password", 361 | "session_timeout": 172800, 362 | "user_type": "normal" 363 | }, 364 | { 365 | "id": 46, 366 | "create_time": 1545297376.931824, 367 | "is_locked": false, 368 | "username": "other_user", 369 | "permissions": [ 370 | "log_mgmt" 371 | ], 372 | "is_deleted": false, 373 | "ip_policy": [ 374 | "*.*.*.*" 375 | ], 376 | "ip_policy_mode": "allow", 377 | "last_change_password": null, 378 | "user_experience_improvement_program": true, 379 | "login_method": "password", 380 | "session_timeout": 172800, 381 | "user_type": "normal" 382 | } 383 | ] 384 | } 385 | ``` 386 | 387 | ## POST 388 | 389 | 数据格式 390 | 391 | |字段名|数据类型|是否必填/默认值|NULL|其他| 392 | |:--|:-:|:-:|:-:|:-:| 393 | |session_timeout | 整型数字 | 非必填/172800 | False | 最小值: 1; | 394 | |username | 字符串 | 必填 | False | 最大长度: 32; | 395 | |password | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | 396 | |login_method | 指定选项 | 必填 | False | 选项是: ['cert', 'password'] | 397 | |permissions | 指定选项 | 必填 | False | 选项是: ['user_mgmt', 'waf_mgmt', 'log_mgmt'] | 398 | |ip_policy | 列表 | 必填 | False | - 详见下方表格 | 399 | |ip_policy_mode | 指定选项 | 必填 | False | 选项是: ['allow', 'deny'] | 400 | 401 | ip_policy 402 | 403 | |字段名|数据类型|是否必填/默认值|NULL|其他| 404 | |:--|:-:|:-:|:-:|:-:| 405 | |子字段 | 字符串 | 必填 | False | 最大长度: 64; | 406 | 407 | 408 | 409 | ### 创建日志管理员 410 | 411 | 412 | 413 | 请求 414 | 415 | ```js 416 | { 417 | "username": "other_user", 418 | "password": "password@user", 419 | "permissions": [ 420 | "log_mgmt" 421 | ], 422 | "ip_policy": [ 423 | "*.*.*.*" 424 | ], 425 | "ip_policy_mode": "allow", 426 | "login_method": "password", 427 | "session_timeout": 60 428 | } 429 | ``` 430 | 431 | 响应 432 | 433 | ```js 434 | { 435 | "err": null, 436 | "msg": "", 437 | "data": { 438 | "id": 29, 439 | "create_time": 1545297375.803614, 440 | "is_locked": false, 441 | "username": "other_user", 442 | "permissions": [ 443 | "log_mgmt" 444 | ], 445 | "is_deleted": false, 446 | "ip_policy": [ 447 | "*.*.*.*" 448 | ], 449 | "ip_policy_mode": "allow", 450 | "last_change_password": null, 451 | "user_experience_improvement_program": true, 452 | "login_method": "password", 453 | "session_timeout": 60, 454 | "user_type": "normal" 455 | } 456 | } 457 | ``` 458 | 459 | ### 创建超级管理员 460 | 461 | 462 | 463 | 请求 464 | 465 | ```js 466 | { 467 | "username": "other_user", 468 | "password": "password@user", 469 | "permissions": [ 470 | "user_mgmt", 471 | "waf_mgmt", 472 | "log_mgmt" 473 | ], 474 | "ip_policy": [ 475 | "*.*.*.*" 476 | ], 477 | "ip_policy_mode": "allow", 478 | "login_method": "password", 479 | "session_timeout": 60 480 | } 481 | ``` 482 | 483 | 响应 484 | 485 | ```js 486 | { 487 | "err": null, 488 | "msg": "", 489 | "data": { 490 | "id": 31, 491 | "create_time": 1545297375.938573, 492 | "is_locked": false, 493 | "username": "other_user", 494 | "permissions": [ 495 | "user_mgmt", 496 | "waf_mgmt", 497 | "log_mgmt" 498 | ], 499 | "is_deleted": false, 500 | "ip_policy": [ 501 | "*.*.*.*" 502 | ], 503 | "ip_policy_mode": "allow", 504 | "last_change_password": null, 505 | "user_experience_improvement_program": true, 506 | "login_method": "password", 507 | "session_timeout": 60, 508 | "user_type": "normal" 509 | } 510 | } 511 | ``` 512 | 513 | ### 创建 waf 管理员 514 | 515 | 516 | 517 | 请求 518 | 519 | ```js 520 | { 521 | "username": "other_user", 522 | "password": "password@user", 523 | "permissions": [ 524 | "waf_mgmt" 525 | ], 526 | "ip_policy": [ 527 | "*.*.*.*" 528 | ], 529 | "ip_policy_mode": "allow", 530 | "login_method": "password", 531 | "session_timeout": 60 532 | } 533 | ``` 534 | 535 | 响应 536 | 537 | ```js 538 | { 539 | "err": null, 540 | "msg": "", 541 | "data": { 542 | "id": 38, 543 | "create_time": 1545297376.426044, 544 | "is_locked": false, 545 | "username": "other_user", 546 | "permissions": [ 547 | "waf_mgmt" 548 | ], 549 | "is_deleted": false, 550 | "ip_policy": [ 551 | "*.*.*.*" 552 | ], 553 | "ip_policy_mode": "allow", 554 | "last_change_password": null, 555 | "user_experience_improvement_program": true, 556 | "login_method": "password", 557 | "session_timeout": 60, 558 | "user_type": "normal" 559 | } 560 | } 561 | ``` 562 | 563 | ## PUT 564 | 565 | 数据格式 566 | 567 | |字段名|数据类型|是否必填/默认值|NULL|其他| 568 | |:--|:-:|:-:|:-:|:-:| 569 | |session_timeout | 整型数字 | 非必填/172800 | False | 最小值: 1; | 570 | |username | 字符串 | 必填 | False | 最大长度: 32; | 571 | |password | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | 572 | |login_method | 指定选项 | 必填 | False | 选项是: ['cert', 'password'] | 573 | |permissions | 指定选项 | 必填 | False | 选项是: ['user_mgmt', 'waf_mgmt', 'log_mgmt'] | 574 | |ip_policy | 列表 | 必填 | False | - 详见下方表格 | 575 | |ip_policy_mode | 指定选项 | 必填 | False | 选项是: ['allow', 'deny'] | 576 | 577 | ip_policy 578 | 579 | |字段名|数据类型|是否必填/默认值|NULL|其他| 580 | |:--|:-:|:-:|:-:|:-:| 581 | |子字段 | 字符串 | 必填 | False | 最大长度: 64; | 582 | 583 | 584 | 585 | ### 修改用户信息 586 | 587 | 588 | 589 | 请求 590 | 591 | ```js 592 | { 593 | "username": "other_user", 594 | "permissions": [ 595 | "user_mgmt", 596 | "waf_mgmt", 597 | "log_mgmt" 598 | ], 599 | "ip_policy": [ 600 | "*.*.*.*" 601 | ], 602 | "ip_policy_mode": "deny", 603 | "login_method": "password", 604 | "session_timeout": 60 605 | } 606 | ``` 607 | 608 | 响应 609 | 610 | ```js 611 | { 612 | "err": null, 613 | "msg": "", 614 | "data": { 615 | "id": 52, 616 | "create_time": 1545297377.20951, 617 | "is_locked": false, 618 | "username": "other_user", 619 | "permissions": [ 620 | "user_mgmt", 621 | "waf_mgmt", 622 | "log_mgmt" 623 | ], 624 | "is_deleted": false, 625 | "ip_policy": [ 626 | "*.*.*.*" 627 | ], 628 | "ip_policy_mode": "deny", 629 | "last_change_password": null, 630 | "user_experience_improvement_program": true, 631 | "login_method": "password", 632 | "session_timeout": 60, 633 | "user_type": "normal" 634 | } 635 | } 636 | ``` 637 | 638 | ### 修改用户密码 639 | 640 | 需要原密码和新密码 641 | 642 | 请求 643 | 644 | ```js 645 | { 646 | "username": "other_user", 647 | "password": "new_password", 648 | "permissions": [ 649 | "user_mgmt", 650 | "waf_mgmt", 651 | "log_mgmt" 652 | ], 653 | "ip_policy": [ 654 | "*.*.*.*" 655 | ], 656 | "ip_policy_mode": "allow", 657 | "login_method": "password" 658 | } 659 | ``` 660 | 661 | 响应 662 | 663 | ```js 664 | { 665 | "err": null, 666 | "msg": "", 667 | "data": { 668 | "id": 62, 669 | "create_time": 1545297377.73444, 670 | "is_locked": false, 671 | "username": "other_user", 672 | "permissions": [ 673 | "user_mgmt", 674 | "waf_mgmt", 675 | "log_mgmt" 676 | ], 677 | "is_deleted": false, 678 | "ip_policy": [ 679 | "*.*.*.*" 680 | ], 681 | "ip_policy_mode": "allow", 682 | "last_change_password": "2018-12-20T09:16:17.804446Z", 683 | "user_experience_improvement_program": true, 684 | "login_method": "password", 685 | "session_timeout": 172800, 686 | "user_type": "normal" 687 | } 688 | } 689 | ``` 690 | 691 | ## DELETE 692 | 693 | 数据格式 694 | 695 | |字段名|数据类型|是否必填/默认值|NULL|其他| 696 | |:--|:-:|:-:|:-:|:-:| 697 | |username | 字符串 | 必填 | False | 最大长度: 32; | 698 | 699 | 700 | 701 | ### 删除用户 702 | 703 | 704 | 705 | 请求 706 | 707 | ```js 708 | { 709 | "username": "other_user" 710 | } 711 | ``` 712 | 713 | 响应 714 | 715 | ```js 716 | { 717 | "err": null, 718 | "msg": "", 719 | "data": null 720 | } 721 | ``` 722 | 723 | # /api/v1/profile/session_timeout 724 | 725 | ## PUT 726 | 727 | 数据格式 728 | 729 | |字段名|数据类型|是否必填/默认值|NULL|其他| 730 | |:--|:-:|:-:|:-:|:-:| 731 | |session_timeout | 整型数字 | 非必填/172800 | False | 最小值: 1; | 732 | 733 | 734 | 735 | ### 用户修改自己的session过期时间 736 | 737 | 738 | 739 | 请求 740 | 741 | ```js 742 | { 743 | "session_timeout": 60 744 | } 745 | ``` 746 | 747 | 响应 748 | 749 | ```js 750 | { 751 | "err": null, 752 | "msg": "", 753 | "data": null 754 | } 755 | ``` 756 | 757 | -------------------------------------------------------------------------------- /series_20/plugin_sdk.md: -------------------------------------------------------------------------------- 1 | # SafeLine 20 扩展插件文档 2 | 3 | 4 | SafeLine 扩展插件系统(以下简称插件系统)使用 Lua 作为脚本语言,**目前语法兼容标准 Lua 5.1。** 5 | 6 | 在「网站防护」→「扩展插件管理」页面可对插件进行新增,查看,删除等操作。本文档将说明插件的编写方法以及可用的相关接口。 7 | 8 | [toc] 9 | 10 | ## 导入 11 | 12 | 在编写插件时,首先需要导入 safeline 模块,safeline 模块包含了插件系统提供的所有接口。 13 | 14 | ```lua 15 | local safeline = require "safeline" 16 | ``` 17 | 注意: 出于安全性的考量,插件脚本中只允许使用有限的标准库函数,其中 IO 库中的所有函数均被禁用,OS 库中仅保留 `time`, `date`, `clock`, `timediff` 等函数。 18 | 19 | ## 注册 20 | 21 | 每个插件至少需要调用一次注册函数以获得一个触发器(Trigger),调用注册函数时通常会指定一个触发条件及其回调函数,当满足条件时该回调函数会被调用。 22 | 注册函数签名如下: 23 | ```lua 24 | function safeline.register(type, ...) 25 | ``` 26 | 第一个参数 type 代表触发器的类型,可能的取值及用途见下表。其后的变长参数的数目及类型随着 type 的不同而不同。 27 | 28 | | 触发器类型 | 用途 | 29 | |:--------|:--------:| 30 | | `safeline.TYPE_MATCH` | 处理符合给定条件的请求 | 31 | | `safeline.TYPE_TICKER` | 执行定时任务 | 32 | | `safeline.TYPE_QUERY` | 处理符合给定查询的请求 | 33 | 34 | ### Match 触发器 35 | 36 | Match 触发器需要用户给定一个描述请求特征(仅限于预定义的一些条件)的表 match 和一个回调函数 process,当通过 SafeLine 的请求满足 match 的描述时,回调函数 process 会在该请求的上下文中被调用。其注册方式如下: 37 | 38 | ```lua 39 | local safeline = require "safeline" 40 | 41 | local match = { 42 | ip = "0.0.0.0/0", 43 | host = [[.*\.chaitin\.cn:80]], 44 | url_path = [[/.*safeline]], 45 | target = safeline.MATCH_TARGET_ALL, 46 | } 47 | 48 | function process(ip, host, url_path) 49 | -- 在此处理请求 50 | end 51 | 52 | safeline.register(safeline.TYPE_PROCESS, match, process) 53 | ``` 54 | `match` 是一个`table`,用来描述要匹配的请求的特征,其支持的字段见下表: 55 | 56 | | 字段 | 类型 | | 用途 | 57 | |:--------|:--------:| :--------:| :--------: | 58 | | ip | 字符串 | | 匹配请求的源 IP,支持匹配单个 IP 地址(v4 或 v6)或使用 CIDR 记法表示的 IP 段 | 59 | | host | 字符串 | | 匹配请求的目的域名,支持 perl 风格的正则表达式。该域名和 SafeLine 「防护站点」中的「域名」+「监听端口」对应,**即匹配时不省略端口号**。 | 60 | | url_path | 字符串 | | 匹配请求的路径,支持 perl 风格的正则表达式。**路径中不包含[请求参数](https://en.wikipedia.org/wiki/Query_string)**| 61 | | target | 枚举类型 | `safeline.MATCH_TARGET_ALL` | 匹配全部请求(默认值)| 62 | | | | `safeline.MATCH_TARGET_ACCESS` | 匹配普通请求 | 63 | | | | `safeline.MATCH_TARGET_DETECT` | 匹配产生「攻击检测日志」的请求(包括被拦截的以及未拦截但产生日志的请求) | 64 | 65 | ``` 66 | 注意:host 和 url_path 中的正则表达式均匹配整个字符串,例如:url_path = "/safeline" 仅匹配访问 “/safeline” 或者 “/safeline?key=xxxx” 的请求,而不匹配访问 “/safeline.html” 的请求。 67 | ``` 68 | ``` 69 | 提示: 70 | 对于正则表达式,建议使用 Lua 的 raw string 语法 [[ ]] 代替双引号以减少转义符的使用。 71 | 当某个字段被省略时,会被视为匹配所有,即 host 被省略时,和 host = [[.*]] 等价。 72 | ``` 73 | 74 | process 是回调函数,当插件触发时被调用,参数的值即为请求对应的源 IP 地址(ip)、目的域名(host)、访问路径(url_path)。函数无返回值。 75 | ``` 76 | 例如:对于一个访问 https://www.chaitin.cn/zh/safeline 的请求,其 host 是 "www.chaitin.cn:443",访问路径是 "/zh/safeline"。 77 | ``` 78 | 在回调函数中,下列函数可获取当前请求的上下文: 79 | - `target = safeline.get_target()` 80 | - 获取当前请求的 target 信息,可能返回值如下: 81 | 82 | | 返回值 | 含义 | 83 | |:--------:|:--------:| 84 | | safeline.MATCH_TARGET_ACCESS | 普通的访问请求 | 85 | | safeline.MATCH_TARGET_DETECT | 产生「攻击检测日志」的请求(包括被拦截的以及未拦截但产生日志的请求)| 86 | 87 | - `session = safeline.get_session()` 88 | - 获取当前请求的 session 信息 89 | - 返回值为 table,其 key 为 session ID,其 value 为 session value。默认的 session ID 是字符串 "session"。 90 | 91 | 92 | - `info = safeline.get_detailed_info()` 93 | - 获取当前请求的详细信息,返回值为 table,其索引随着 target 的不同。 94 | - 返回值为 table,当请求的 target 是 `safeline.MATCH_TARGET_ACCESS` 是,其内容为: 95 | 96 | | 字段 | 类型 | 含义 | 97 | |:--------:|:--------:|:--------:| 98 | | scheme | 字符串 | 请求的协议,目前可能的取值为 "http"或 "https"| 99 | | method | 字符串 | 请求的[HTTP请求方法](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods) | 100 | | host | 字符串 | 请求的目标域名 | 101 | | port | 整数 | 请求的目标端口 | 102 | | url_path | 字符串 | 请求的路径,不包含请求参数 | 103 | | timestamp | 时间 | 请求到达服务器时的时间戳 | 104 | | time | 浮点数 | 请求消耗业务服务器的时间,单位为秒 | 105 | | status_code| 整数 | 请求的[HTTP响应状态码](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status) | 106 | | ip | 字符串 | 请求的源 IP 地址 | 107 | | req_block_reason | 字符串 | 请求被拦截的原因,可能的取值为 "web" 或 "acl",分别代表被 检测引擎 和 访问控制拦截。请求未被拦截时该字段被省略 | 108 | | resp_block_reason | 字符串 | 该请求的响应被拦截的原因,取值同 req_block_reason | 109 | - 当请求的 target 是 `safeline.MATCH_TARGET_DETECT` 时,其部分内容为: 110 | 111 | | 字段 | 类型 | 含义 | 112 | |:--------:|:--------:|:--------:| 113 | | node | 字符串 | 检测服务器节点名称 | 114 | | timestamp | 整数 | 请求的 Unix 时间戳 | 115 | | host | 字符串 | 请求的目标域名 | 116 | | urlpath | 字符串 | 请求的访问路径 | 117 | | method | 字符串 | 请求的[HTTP请求方法](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods) | 118 | | req_header_raw | 字符串 | 经过编码的 HTTP 请求头 | 119 | | resp_header_raw | 字符串 | 经过编码的 HTTP 响应头 | 120 | | body | 字符串 | 请求体 | 121 | | resp_body | 字符串 | 响应体 | 122 | | src_ip | 字符串 | 请求的源 IP | 123 | | dest_ip | 字符串 | 请求的目的 IP | 124 | | attack_type | 整数 | 请求的攻击类型 | 125 | | risk_level | 字符串 | 请求的风险级别 | 126 | | event_id | 字符串 | 请求的时间 ID | 127 | | rule_id | 字符串 | 命中该次请求的规则 ID | 128 | | user_agent | 字符串 | User Agent | 129 | | action | 整数 | SafeLine 对此请求执行的动作(拦截/放行) | 130 | 131 | 132 | ### Ticker 触发器 133 | Ticker 触发器会根据用户指定的周期定时地调用回调函数,**在集群部署模式下亦能保证该定时器的正确性**。其注册方式如下: 134 | ```lua 135 | local safeline = require "safeline" 136 | 137 | local duration = 10 138 | 139 | function tick(dur) 140 | -- 在此处理定时任务 141 | end 142 | 143 | safeline.register(safeline.TYPE_TICKER, duration, tick) 144 | ``` 145 | - durtaion 是一个整数,用来声明插件的触发间隔,其单位为秒。 146 | 147 | - ticker 是插件的回调函数,当插件触发时被调用,参数 dur 的值为插件的触发间隔,其单位为秒。函数无返回值。 148 | 149 | ### Query 触发器 150 | Query 触发器允许用户使用类 SQL 的查询语言对请求进行流式的统计,每次统计结果产生时会将其传递给回调函数。其注册方式如下: 151 | ```lua 152 | safeline = require("safeline") 153 | 154 | query = [[SELECT ip FROM access_log WHERE time > 0.01]] 155 | 156 | function process(key, rows) 157 | -- 在此处理查询结果 158 | end) 159 | 160 | safeline.register(safeline.TYPE_QUERY, query, process) 161 | ``` 162 | - query 是用户指定的查询的语句,用法如下 163 | - Plumber SQL 使用 164 | ```sql 165 | --当前支持的语法如下: 166 | SELECT [ DISTINCT ] 167 | expression [ [ AS ] output_name ] [, ...] 168 | [ FROM source_name ] 169 | [ WHERE condition ] 170 | [ GROUP BY window | expression [, ...] ] 171 | [ HAVING condition [, ...] ] 172 | [ ORDER BY expression [ ASC | DESC ] ] 173 | [ LIMIT count ] 174 | [ OFFSET start ]; 175 | ``` 176 | - Source 177 | ```sql 178 | --Source 代表了一个数据流,当前仅支持在 FROM 子句中直接指定 source_name。 179 | --指定数据源后可在其他字句中直接引用流中的字段名。 180 | SELECT word form article; 181 | SELECT article.word form article; 182 | ``` 183 | - 函数 184 | - 窗口函数 185 | 186 | 窗口函数将无边界的数据流按照某种策略划分为有边界的数据分组,每个数据分组被成为一个「窗口」。 187 | 188 | 窗口函数只能在 GROUP BY 子句中使用,在表达式中被引用时,窗口函数的返回值为该窗口的 ID(对于时间窗口,该 ID 为窗口的起始时间戳)。 189 | ``` 190 | 注意: 当前仅支持时间窗口。 191 | ``` 192 | - `TUMBLE_WINDOW(timestamp, size)`:使用滚动时间窗口,`timestamp` 为划分的时间戳,若数据流中的元素不包含可用的时间戳,可以使用 本地时间 `NOW()` 替代,`size` 为以秒为单位的窗口大小。 193 | - `TUMBLE_WINDOW(timestamp, size, slide)`:使用滑动时间窗口,`timestamp` 为划分的时间戳,若数据流中的元素不包含可用的时间戳,可以使用 本地时间 `NOW()` 替代,`size` 为以秒为单位的窗口大小,`offset` 为以秒为单位的窗口滑动步长。 194 | - 聚合函数 195 | 聚合函数将数据流中的分组聚合为单个元素。 196 | 聚合函数只能在 GROUP BY 中指定了 key 的情况下使用,且不允许在 WHERE 子句中使用。 197 | - `COUNT()`:统计当前分组中元素的个数。 198 | ``` 199 | 注意: 暂不支持 COUNT(*)。 200 | ``` 201 | - `SUM()`:对当前分组中的元素求和。 202 | - 标记函数 203 | 标量函数可以作为任意表达式使用。 204 | - `NOW()`:返回当前时间。 205 | 206 | - 目前可用的数据源为 "access_log" ,其内容为当前所有请求的概要数据,具体字段如下: 207 | 208 | | 字段 | 类型 | 含义 | 209 | |:--------:|:--------:|:--------:| 210 | | scheme | 字符串 | 请求的协议,目前可能的取值为 "http"或 "https"| 211 | | method | 字符串 | 请求的[HTTP请求方法](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Methods) | 212 | | host | 字符串 | 请求的目标域名 | 213 | | port | 整数 | 请求的目标端口 | 214 | | url_path | 字符串 | 请求的路径,不包含请求参数 | 215 | | timestamp | 时间 | 请求到达服务器时的时间戳 | 216 | | time | 浮点数 | 请求消耗业务服务器的时间,单位为秒 | 217 | | status_code| 整数 | 请求的[HTTP响应状态码](https://developer.mozilla.org/zh-CN/docs/Web/HTTP/Status) | 218 | | ip | 字符串 | 请求的源 IP 地址 | 219 | | req_block_reason | 字符串 | 请求被拦截的原因,可能的取值为 "web" 或 "acl",分别代表被 检测引擎 和 访问控制拦截。请求未被拦截时该字段被省略 | 220 | | resp_block_reason | 字符串 | 该请求的响应被拦截的原因,取值同 req_block_reason | 221 | 查询每次返回的结果会作为参数传递给回调函数,若返回结果为数据分组,参数 key 即为分组的 key,参数 rows 即为分组内容;若返回结果为单个元素,则参数 key 为 nil,参数 rows 仅包含一个元素。 222 | ``` 223 | 注意:请勿使用 pairs 或 ipairs 遍历 rows 对象,直接 for row in rows do 即可。 224 | ``` 225 | ```lua 226 | -- 示例 227 | -- 查询 5 秒内访问超过 100 次的前 10 个 IP: 228 | SELECT ip FROM access_log GROUP BY TUMBLE_WINDOW(timestamp, 5), ip HAING COUNT(ip) > 100 ORDER BY COUNT(ip) DESC LIMIT 10 229 | -- 查询每次请求时间超过 0.01 秒的请求的信息: 230 | SELECT ip, (scheme + "://" + host + url_path) as url FROM access_log WHERE time > 0.01 231 | ``` 232 | 233 | ## 接口函数 234 | 235 | 此处列举在扩展插件可供使用的接口函数。所有接口函数均位于 safeline 包内,以 “返回值 = 函数名(参数)”形式描述其签名,多个返回值和参数之间以英文逗号 "," 分隔,变长参数以三个句号 "..." 表示。 236 | 237 | ### 访问频率控制 238 | SafeLine 的「访问控制规则」的部分接口。 239 | 表 key 定义了限制条件: 240 | 241 | | 字段 | 含义 | 242 | |:--------:|:--------:| 243 | | scheme | 限制特定协议的请求,默认为 "http" | 244 | | ip | 限制源 IP 的访问 | 245 | | session | 限制 session 的访问 | 246 | | host | 限制对 host 的访问 | 247 | | url_path | 限制对特定地址的访问 | 248 | 249 | 枚举值 scope 修饰了 key 中的定义: 250 | 251 | | 值 | 含义 | 252 | |:--------:|:--------:| 253 | | safeline.ACTION_SCOPE_ALL | 限制所有请求 | 254 | | safeline.ACTION_SCOPE_URL | 限制访问特定地址的请求 | 255 | | safeline.ACTION_SCOPE_URLPREFIX | 限制访问某特定前缀地址的请求 | 256 | 257 | - `err = safeline.action_ban(scope, key, duration)` 258 | - 封禁符合 key 约束的请求,有效时间为 duration 秒。 259 | - duration 为整型,以秒为单位。 260 | - 提交请求成功时函数返回 nil,失败时返回包含错误信息的字符串。 261 | - `err = safeline.action_limit(scope, key, duration, period, limit)` 262 | - 限制符合 key 约束的请求的访问频率,在 period 内只允许访问 limit 次,有效时间为 duration 秒。 263 | - duration 为整型,以秒为单位;period 为整型,以秒为单位;limit 为整型。 264 | - 提交请求成功时函数返回 nil,失败时返回包含错误信息的字符串。 265 | 266 | ### KV 存储 267 | 268 | 插件系统内置的键值对型数据库(key-value database)接口,所有接口函数的第一个参数需要指定在哪个 DB 中操作,可选值为如下: 269 | 270 | | DB | 用途| 271 | |:--------:|:--------:| 272 | | `safeline.DB_LOCAL` | 对于每个插件是独立的,不同插件之间互不干扰 | 273 | | `safeline.DB_GLOBAL` |在所有插件间共享,不同插件直接可以交换数据 | 274 | 275 | **DB 中的 key 和 value 类型均为字符串**。对于 safeline.db_add 以及 safeline.db_sub 函数,其返回值会自动转换为数字。 276 | ``` 277 | 提示:从性能角度考虑,使用 safeline.DB_LOCAL 可以获得更高的性能。 278 | ``` 279 | 280 | - `val = safeline.db_get(db, key)` 281 | - 从指定 DB 中获取 key 对应的值。 282 | - 当 key 存在时,返回值为字符串,否则返回 nil。 283 | - `safeline.db_set(db, key, val)` 284 | - 设定指定 DB 中 key 的值为 val。 285 | - 无返回值。 286 | - `safeline.db_del(db, key)` 287 | - 从指定 DB 中删除 key。 288 | - `size = safeline.db_size(db)` 289 | - 获取指定 DB 中 key 数目。 290 | - 返回值为整数。 291 | - `safeline.db_clear(db)` 292 | - 清空指定 DB。 293 | - `result = safeline.db_add(db, key, val)` 294 | - 给指定 DB 中 key 的值加上浮点数 val,即 db[key] += val,key 不需要事先创建,初始值为 0。 295 | - 返回累加后的值,类型为浮点数。 296 | ``` 297 | 注意:直接对该 key 调用 safeline.db_get 会返回字符串形式的值。 298 | ``` 299 | - `result = safeline.db_sub(db, key, val)` 300 | - 给指定 DB 中 key 的值减去浮点数 val,即 db[key] -= val,key 不需要事先创建,初始值为 0。 301 | - 返回累加后的值,类型为浮点数。 302 | ``` 303 | 注意:直接对该 key 调用 safeline.db_get 会返回字符串形式的值。 304 | ``` 305 | 306 | ### HTTP 客户端 307 | 308 | - `resp, err = safeline.http_get(url, header)` 309 | - 对地址 url 发起一次 HTTP GET 请求,url 中可携带 GET 参数,在 header 中可指定 HTTP header。 310 | - url 为字符串类型,header 为 table 类型。 311 | - 返回值为中 err 为字符串类型,包含请求错误信息,无错误时值为 nil,当无错误发生时resp 为 table 类型,包含 HTTP 请求的响应,其结构示例如下: 312 | ``` 313 | { 314 | status_code = 200, 315 | header = { 316 | -- ... 317 | }, 318 | body = "Response body", 319 | } 320 | ``` 321 | - `resp, err = safeline.http_post(url, header, data)` 322 | - 对地址 url 发起一次 HTTP POST 请求,在 header 中可指定 HTTP header,data 为 POST 数据。 323 | - url 为字符串类型,header 为 table 类型,data 为字符串类型。 324 | - 返回值同 safeline.http_get()。 325 | 326 | ### 插件日志 327 | - `safeline.log(tag, msg)` 328 | - 产生一条插件日志,日志可以在 SafeLine 管理界面「检测日志」→ 「扩展插件日志」中查看。 329 | - tagtag 被插件系统内部保留使用,msg 是日志内容,tag 和 msg 均不得为空。 和 msg 均为字符串类型,tag 用于标记日志的类别,值为 "system" 的 330 | - 函数无返回值。 331 | 332 | 333 | ### 异步支持 334 | 对于某些需要等待返回结果的操作(比如 action_ban),我们可能需要在较短时间内执行多次(比如封禁 1000 个查询到的违规用户),此时我们就需要将这些任务异步化。 335 | 336 | 本 API 支持 Promise 风格的异步。一个 Promise 定义了一个异步的执行流。Promise 由 Promise 链节点组成,Promise 链中一个节点包含对上一节点返回的结果的处理。 337 | 338 | 比如,我们希望封禁一个用户,并且在日志中记录封禁的成功与否及失败原因。此时,日志记录就应该作为封禁操作的下一个Promise链条节点,因为它需要封禁的结果。 339 | 340 | 和 Promise 有关的 API 有: 341 | - `promiseObject = safeline.promise(func, ...)` 342 | - 创建 Promise 对象并返回,其中 func 是需要执行的异步函数,后面的...是该函数的参数。 343 | - `newPromiseObject = promiseObject(func, ...)` 344 | - 往一个 promise 后添加一个 Promise 链的节点,并返回添加节点后的 Promise,func 是该节点要执行的异步函数,...是该函数的参数。 345 | - 注意,Promise 链条的上一个节点的返回值是当前节点的函数参数,且出现在通过...指定的参数后。 346 | - `promiseObject()` 347 | - 提交一个 Promise 对象,使之能执行。注意,一个 Promise 对象如果不提交,那它不会执行。 348 | - Promise 的执行由 lua 插件的调度器决定在合适的时机执行。 349 | - 以封禁一群用户并记录相应日志为例 350 | ```lua 351 | --同步执行(每封禁一个用户之前必须等待前一个用户的封禁结果返回并日志): 352 | safeline = require("safeline") 353 | 354 | safeline.register(safeline.TYPE_QUERY, [[ 355 | SELECT 356 | TUMBLE_WINDOW(timestamp, 1) AS timestamp, 357 | ip, 358 | COUNT(ip) AS count_ip, 359 | SUM(time) AS interval 360 | FROM access_log 361 | GROUP BY TUMBLE_WINDOW(timestamp, 1), ip 362 | HAVING count_ip > 2000 363 | ORDER BY count_ip desc 364 | ]], function(key, rows) 365 | for r in rows do 366 | err = safeline.action_ban(safeline.ACTION_SCOPE_URLPREFIX, { 367 | ip = r.ip, 368 | host = "myhost.com", 369 | urlpath = "/", 370 | scheme = "http" 371 | }, 0) 372 | if err == nil then 373 | safeline.log("Banned user " .. r.ip .. " from accessing.") 374 | else 375 | safeline.log("Cannot ban user " .. r.ip .. " due to error: " .. err) 376 | end 377 | end 378 | end) 379 | --使用 Promise 的例子(封禁不同用户并日志的操作互不相关,在 lua 引擎看来就是不同的执行流): 380 | safeline = require("safeline") 381 | 382 | safeline.register(safeline.TYPE_QUERY, [[ 383 | SELECT 384 | TUMBLE_WINDOW(timestamp, 1) AS timestamp, 385 | ip, 386 | COUNT(ip) AS count_ip, 387 | SUM(time) AS interval 388 | FROM access_log 389 | GROUP BY TUMBLE_WINDOW(timestamp, 1), ip 390 | HAVING count_ip > 2000 391 | ORDER BY count_ip desc 392 | ]], function(key, rows) 393 | for r in rows do 394 | err = safeline.action_ban(safeline.ACTION_SCOPE_URLPREFIX, { 395 | ip = r.ip, 396 | host = "myhost.com", 397 | urlpath = "/", 398 | scheme = "http" 399 | }, 0)(function(err) 400 | if err == nil then 401 | safeline.log("Banned user " .. r.ip .. " from accessing.") 402 | else 403 | safeline.log("Cannot ban user " .. r.ip .. " due to error: " .. err) 404 | end 405 | end)() 406 | end 407 | end) 408 | ``` 409 | 410 | ## 示例 411 | 一个典型的插件如下所示: 412 | ```lua 413 | local safeline = require "safeline" 414 | 415 | query = [[ SELECT ip, (scheme + '://' + host + url_path) as url, time FROM access_log WHERE time > 0.01 ]] 416 | 417 | function process(key, rows) 418 | for row in rows do 419 | safeline.log("耗时请求", "IP: " .. row.ip .. ", URL: " .. row.url .. ", time: " .. row.time) 420 | end 421 | end 422 | 423 | 424 | 425 | safeline.register(safeline.TYPE_QUERY, query, process) 426 | ``` 427 | 428 | -------------------------------------------------------------------------------- /series_10/api_doc/serializer.json: -------------------------------------------------------------------------------- 1 | { 2 | "/api/v1/user/cert:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|username | 字符串 | 必填 | False | 最大长度: 32; | \n\n", 3 | "/api/v1/user/unlock:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|username | 字符串 | 必填 | False | 最大长度: 32; | \n\n", 4 | "/api/v1/user:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|session_timeout | 整型数字 | 非必填/172800 | False | 最小值: 1; | \n|username | 字符串 | 必填 | False | 最大长度: 32; | \n|password | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|login_method | 指定选项 | 必填 | False | 选项是: ['cert', 'password'] | \n|permissions | 指定选项 | 必填 | False | 选项是: ['user_mgmt', 'waf_mgmt', 'log_mgmt'] | \n|ip_policy | 列表 | 必填 | False | - 详见下方表格 | \n|ip_policy_mode | 指定选项 | 必填 | False | 选项是: ['allow', 'deny'] | \n\nip_policy\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | 最大长度: 64; | \n\n", 5 | "/api/v1/user:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|username | 字符串 | 必填 | False | 最大长度: 32; | \n\n", 6 | "/api/v1/user:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|session_timeout | 整型数字 | 非必填/172800 | False | 最小值: 1; | \n|username | 字符串 | 必填 | False | 最大长度: 32; | \n|password | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|login_method | 指定选项 | 必填 | False | 选项是: ['cert', 'password'] | \n|permissions | 指定选项 | 必填 | False | 选项是: ['user_mgmt', 'waf_mgmt', 'log_mgmt'] | \n|ip_policy | 列表 | 必填 | False | - 详见下方表格 | \n|ip_policy_mode | 指定选项 | 必填 | False | 选项是: ['allow', 'deny'] | \n\nip_policy\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | 最大长度: 64; | \n\n", 7 | "/api/v1/login:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|username | 字符串 | 必填 | False | 最大长度: 32; | \n|password | 字符串 | 必填 | False | 最大长度: 32; | \n\n", 8 | "/api/v1/profile/session_timeout:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|session_timeout | 整型数字 | 非必填/172800 | False | 最小值: 1; | \n\n", 9 | "/api/v1/acl_rule:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|expire_time | 浮点数字 | 非必填/无默认值 | True | 绝对过期时间,时间戳; | \n|expire_after | 整型数字 | 非必填/无默认值 | True | 多少秒之后过期,和时间戳只传一个即可; 最小值: 1; | \n|domain | 字符串 | 非必填/无默认值 | True | - | \n|urlpath | 字符串 | 非必填/无默认值 | True | - | \n|urlpath_match_method | 指定选项 | 非必填/'str' | False | 选项是: ['str', 'prefix'] | \n|action | 指定选项 | 必填 | False | 选项是: ['forbid', 'limit_rate'] | \n|limit_rate_period | 整型数字 | 非必填/无默认值 | True | 最小值: 1; | \n|limit_rate_limit | 整型数字 | 非必填/无默认值 | True | 最小值: 1; | \n|comment | 字符串 | 非必填/无默认值 | False | 最大长度: 128; 最小长度: 0; | \n|cidr_list | 列表 | 非必填/[] | False | - 详见下方表格 | \n|on_duplicate | 指定选项 | 非必填/'error' | False | 报错或跳过; 选项是: ['skip', 'error'] | \n|status | 指定选项 | 非必填/'enabled' | False | 选项是: ['enabled', 'disabled'] | \n|forbid_type | 指定选项 | 非必填/'cidr' | False | 选项是: ['cidr', 'session'] | \n|session_list | 列表 | 非必填/[] | False | - 详见下方表格 | \n\ncidr_list\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | IPv4的 IP 或者子网形式字符串; | \n\n\nsession_list\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | - | \n\n", 10 | "/api/v1/acl_rule/delete:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|ids | 列表 | 必填 | False | - 详见下方表格 | \n\nids\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 整型数字 | 必填 | False | - | \n\n", 11 | "/api/v1/acl_rule/disable:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|ids | 列表 | 必填 | False | - 详见下方表格 | \n\nids\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 整型数字 | 必填 | False | - | \n\n", 12 | "/api/v1/acl_rule:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|expire_time | 浮点数字 | 非必填/无默认值 | True | 绝对过期时间,时间戳; | \n|expire_after | 整型数字 | 非必填/无默认值 | True | 多少秒之后过期,和时间戳只传一个即可; 最小值: 1; | \n|domain | 字符串 | 非必填/无默认值 | True | - | \n|urlpath | 字符串 | 非必填/无默认值 | True | - | \n|urlpath_match_method | 指定选项 | 非必填/'str' | False | 选项是: ['str', 'prefix'] | \n|action | 指定选项 | 必填 | False | 选项是: ['forbid', 'limit_rate'] | \n|limit_rate_period | 整型数字 | 非必填/无默认值 | True | 最小值: 1; | \n|limit_rate_limit | 整型数字 | 非必填/无默认值 | True | 最小值: 1; | \n|comment | 字符串 | 非必填/无默认值 | False | 最大长度: 128; 最小长度: 0; | \n|cidr_list | 列表 | 非必填/[] | False | - 详见下方表格 | \n|on_duplicate | 指定选项 | 非必填/'error' | False | 报错或跳过; 选项是: ['skip', 'error'] | \n|status | 指定选项 | 非必填/'enabled' | False | 选项是: ['enabled', 'disabled'] | \n|forbid_type | 指定选项 | 非必填/'cidr' | False | 选项是: ['cidr', 'session'] | \n|session_list | 列表 | 非必填/[] | False | - 详见下方表格 | \n|id | 整型数字 | 必填 | False | - | \n\ncidr_list\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | IPv4的 IP 或者子网形式字符串; | \n\n\nsession_list\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | - | \n\n", 13 | "/api/v1/acl/analyze:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_before | 浮点数字 | 非必填/无默认值 | False | - | \n|time_after | 浮点数字 | 非必填/无默认值 | False | - | \n|group_by | 字符串 | 非必填/无默认值 | False | - | \n|order_by | 字符串 | 非必填/无默认值 | False | - | \n|ip | 字符串 | 非必填/无默认值 | False | - | \n|domain | 字符串 | 非必填/无默认值 | False | - | \n|urlpath | 字符串 | 非必填/无默认值 | False | - | \n|offset | 整型数字 | 非必填/0 | False | - | \n|count | 整型数字 | 非必填/10 | False | - | \n\n", 14 | "/api/v1/acl/white_list:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|cidr | 字符串 | 必填 | False | IPv4的 IP 或者子网形式字符串; | \n|comment | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n\n", 15 | "/api/v1/acl/white_list:DELETE": "数据格式\n\n> 只需要传递 ID\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n\n", 16 | "/api/v1/acl/white_list:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|cidr | 字符串 | 必填 | False | IPv4的 IP 或者子网形式字符串; | \n|comment | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|id | 整型数字 | 必填 | False | - | \n\n", 17 | "/api/v1/acl/log/delete:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|cidr | 字符串 | 非必填/无默认值 | False | - | \n|domain | 字符串 | 非必填/无默认值 | False | - | \n|urlpath | 字符串 | 非必填/无默认值 | False | - | \n|rule_id | 整型数字 | 非必填/无默认值 | False | - | \n\n", 18 | "/api/v1/acl/log/download:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|cidr | 字符串 | 非必填/无默认值 | False | - | \n|domain | 字符串 | 非必填/无默认值 | False | - | \n|urlpath | 字符串 | 非必填/无默认值 | False | - | \n|rule_id | 整型数字 | 非必填/无默认值 | False | - | \n\n", 19 | "/api/v1/acl/ip_comment:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|ip | 字符串 | 必填 | False | - | \n|comment | 字符串 | 必填 | True | 最大长度: 128; 最小长度: 0; | \n\n", 20 | "/api/v1/conf/ha:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|interface_name | 字符串 | 必填 | False | 最大长度: 128; | \n|mode | 指定选项 | 必填 | False | 选项是: ['master', 'backup'] | \n|status | 布尔 | 必填 | False | - | \n|nopreempt | 布尔 | 必填 | False | - | \n\n", 21 | "/api/v1/conf/waf_state/log_ip_source:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|log_ip_source | 列表 | 必填 | False | - 详见下方表格 | \n\nlog_ip_source\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | 最大长度: 64; | \n\n", 22 | "/api/v1/stat/node/delete:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|node | 字符串 | 必填 | False | - | \n\n", 23 | "/api/v3/snserver/heartbeat:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|timestamp | 浮点数字 | 必填 | False | - | \n|time_from | 浮点数字 | 必填 | False | - | \n|node | 字符串 | 必填 | False | 最大长度: 128; | \n|cpu | 浮点数字 | 必填 | False | - | \n|mem | 浮点数字 | 必填 | False | - | \n|req_num_total | 整型数字 | 必填 | False | - | \n|req_num_blocked | 整型数字 | 必填 | False | - | \n|req_num_accepted | 整型数字 | 必填 | False | - | \n|req_num_handled | 整型数字 | 必填 | False | - | \n|req_num_dropped | 整型数字 | 必填 | False | - | \n|req_average_time | 浮点数字 | 必填 | False | - | \n|ioread | 浮点数字 | 必填 | False | - | \n|iowrite | 浮点数字 | 必填 | False | - | \n|req_ngx_time | 整型数字 | 必填 | False | - | \n|req_ngx_count | 整型数字 | 必填 | False | - | \n|detector_version | 整型数字 | 必填 | False | - | \n|workers | 列表 | 必填 | False | - 详见下方表格 | \n|policy_version | 字典 | - | - | 详见下面表格 | \n\nworkers\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|index | 整型数字 | 必填 | False | - | \n|cpu | 浮点数字 | 必填 | False | - | \n|ioread | 浮点数字 | 必填 | False | - | \n|iowrite | 浮点数字 | 必填 | False | - | \n\n\npolicy_version\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|waf_rule_version | 整型数字 | 必填 | False | - | \n|cc_rule_version | 整型数字 | 必填 | False | - | \n\n", 24 | "/api/v1/conf/syslog:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|ip | 字符串 | 必填 | False | - | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|format | 字典 | - | - | 详见下面表格 | \n\nformat\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|type | 指定选项 | 必填 | False | 选项是: ['json', 'custom'] | \n|data | 列表 | 非必填/无默认值 | False | - 详见下方表格 | \n\ndata\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n\n", 25 | "/api/v1/conf/system_service:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|services | 指定选项 | 必填 | False | 选项是: ['web_management', 'icmp_echo', 'ssh', 'snmp'] | \n\n", 26 | "/api/v1/conf/system_service:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|type | 指定选项 | 必填 | False | 选项是: ['web_management', 'icmp_echo', 'ssh', 'snmp'] | \n|interface | 字符串 | 必填 | False | 最大长度: 128; | \n|enabled | 布尔 | 必填 | False | - | \n|port | 整型数字 | 非必填/0 | False | 最小值: 1; 最大值: 65535 | \n\n", 27 | "/api/v1/conf2/backend:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|name | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n|hostname | 字符串 | 必填 | False | - | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n\n", 28 | "/api/v1/conf2/backend:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n|force | 布尔 | 非必填/False | False | - | \n\n", 29 | "/api/v1/conf2/site_backend:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|site_id | 整型数字 | 必填 | False | - | \n|backend_id | 整型数字 | 必填 | False | - | \n|weight | 整型数字 | 必填 | False | 最小值: 1; | \n|is_backup | 布尔 | 非必填/False | False | - | \n\n", 30 | "/api/v1/conf2/site_backend:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|site_id | 整型数字 | 必填 | False | - | \n|backend_id | 整型数字 | 必填 | False | - | \n\n", 31 | "/api/v1/conf2/site_backend:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|site_id | 整型数字 | 必填 | False | - | \n|backend_id | 整型数字 | 必填 | False | - | \n|weight | 整型数字 | 必填 | False | 最小值: 1; | \n|is_backup | 布尔 | 非必填/False | False | - | \n\n", 32 | "/api/v1/conf2/fast_config/s10:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|sites | 字典列表 | - | - | 详见下面表格 | \n\nsites\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | IP 或域名或者泛域名; | \n|cert_id | 整型数字 | 非必填/无默认值 | False | - | \n|backends | 字典列表 | - | - | 详见下面表格 | \n\nbackends\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | IP 或域名或者泛域名; | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n\n", 33 | "/api/v1/conf2/fast_config/w10:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|mode | 指定选项 | 必填 | False | 选项是: ['reverse_proxy', 'transparent_proxy', 'route_proxy'] | \n|inet_mode | 指定选项 | 必填 | False | 选项是: ['dhcp', 'static'] | \n|interface | 字符串 | 必填 | False | - | \n|listeners | 字典列表 | - | - | 详见下面表格 | \n\nlisteners\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|sites | 字典列表 | - | - | 详见下面表格 | \n|hostname | 字符串 | 必填 | False | - | \n\nsites\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | IP 或域名或者泛域名; | \n|cert_id | 整型数字 | 非必填/无默认值 | False | - | \n|backends | 字典列表 | - | - | 详见下面表格 | \n\nbackends\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | IP 或域名或者泛域名; | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n\n", 34 | "/api/v1/conf2/health_check_options:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n|is_enabled | 布尔 | 必填 | False | - | \n|check_http_expect_alive | 指定选项 | 必填 | False | 选项是: ['http_2xx', 'http_3xx', 'http_4xx', 'http_5xx'] | \n|check_type | 指定选项 | 非必填/'http' | False | 选项是: ['tcp', 'http'] | \n|interval | 整型数字 | 非必填/30000 | False | - | \n|fall | 整型数字 | 非必填/5 | False | - | \n|rise | 整型数字 | 非必填/2 | False | - | \n|timeout | 整型数字 | 非必填/1000 | False | - | \n|port | 整型数字 | 非必填/0 | False | - | \n|http_send | 字符串 | 非必填/'GET / HTTP/1.0\\r\\nUser-Agent: SafeLine Health Check\\r\\n\\r\\n' | False | - | \n\n", 35 | "/api/v1/conf2/listener:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|name | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|interface | 字符串 | 必填 | False | - | \n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n|hostname | 字符串 | 必填 | False | - | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n\n", 36 | "/api/v1/conf2/listener:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|name | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|interface | 字符串 | 必填 | False | - | \n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n|hostname | 字符串 | 必填 | False | - | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|id | 整型数字 | 必填 | False | - | \n|is_disabled | 布尔 | 非必填/False | False | - | \n\n", 37 | "/api/v1/conf2/policy/group:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|name | 字符串 | 必填 | False | - | \n\n", 38 | "/api/v1/conf2/policy/group:DELETE": "数据格式\n\n> 只需要传递 ID\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n\n", 39 | "/api/v1/conf2/policy/group:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n|name | 字符串 | 非必填/无默认值 | False | - | \n|modules_switch | 指定选项 | 非必填/'enabled' | False | 选项是: ['enabled', 'disabled'] | \n|detect_response_switch | 指定选项 | 非必填/'enabled' | False | 选项是: ['enabled', 'disabled'] | \n|modules_state | 字典类型 | - | - | 字典类型 | \n|policies_state | 指定选项 | 非必填/'enabled' | False | 选项是: ['enabled', 'disabled'] | \n|misc_detect_config | 字典类型 | - | - | 字典类型 | \n|is_timeout_enabled | 布尔 | 必填 | False | - | \n|detection_timeout_threshold | 整型数字 | 必填 | False | 最小值: 0; 最大值: 1000000 | \n\nmodules_state\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|state | 指定选项 | 必填 | False | 选项是: ['enabled', 'disabled', 'dry_run'] | \n|high_risk_action | 指定选项 | 必填 | False | 选项是: ['deny', 'continue'] | \n|high_risk_enable_log | 指定选项 | 必填 | False | 选项是: [0, 1] | \n|medium_risk_action | 指定选项 | 必填 | False | 选项是: ['deny', 'continue'] | \n|medium_risk_enable_log | 指定选项 | 必填 | False | 选项是: [0, 1] | \n|low_risk_action | 指定选项 | 必填 | False | 选项是: ['deny', 'continue'] | \n|low_risk_enable_log | 指定选项 | 必填 | False | 选项是: [0, 1] | \n|detect_config | 字典类型 | - | - | 字典类型 | \n\ndetect_config\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n\n\nmisc_detect_config\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n\n", 40 | "/api/v1/conf2/policy/rule:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 字符串 | 必填 | False | - | \n\n", 41 | "/api/v1/conf2/policy/rule:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|pattern | 字符串 | 必填 | False | - | \n|state | 指定选项 | 必填 | False | 选项是: ['enabled', 'disabled', 'dry_run'] | \n|action | 指定选项 | 必填 | False | 选项是: ['deny', 'allow'] | \n|comment | 字符串 | 必填 | False | 最大长度: 48; | \n|enable_log | 布尔 | 必填 | False | - | \n|expire_time | 整型数字 | 必填 | False | - | \n|attack_type | 指定选项 | 必填 | False | 选项是: ['none', 'sql_injection', 'xss', 'csrf', 'ssrf', 'dos', 'backdoor', 'deserialization', 'code_execution', 'code_injection', 'command_injection', 'file_upload', 'file_inclusion', 'redirect', 'weak_permission', 'info_leak', 'unauthorized_access', 'unsafe_config', 'xxe', 'xpath_injection', 'ldap_injection', 'directory_traversal', 'scanner', 'permission_bypass', 'acl_bypass', 'file_write', 'file_download', 'file_deletion', 'logic_error', 'crlf_injection', 'ssti', 'click_hijacking', 'buffer_overflow', 'integer_overflow', 'format_string', 'race_condition', 'timeout', 'unknown'] | \n|id | 字符串 | 必填 | False | - | \n\n", 42 | "/api/v1/conf2/policy/rule_priority:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|policy_group_id | 整型数字 | 必填 | False | - | \n|rule_id_list | 列表 | 必填 | False | - 详见下方表格 | \n\nrule_id_list\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | 最大长度: 48; | \n\n", 43 | "/api/v1/conf2/cert:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n|force | 布尔 | 非必填/False | False | - | \n\n", 44 | "/api/v1/conf2/site:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | IP 或域名或者泛域名; | \n|listener_id | 整型数字 | 必填 | False | - | \n|cert_id | 整型数字 | 非必填/无默认值 | True | - | \n\n", 45 | "/api/v1/conf2/site:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 整型数字 | 必填 | False | - | \n|hostname | 字符串 | 必填 | False | IP 或域名或者泛域名; | \n|cert_id | 整型数字 | 非必填/无默认值 | True | - | \n|is_disabled | 布尔 | 非必填/False | False | - | \n|load_balance_policy | 指定选项 | 非必填/无默认值 | False | 选项是: ['round_bin', 'least_conn', 'ip_hash'] | \n|keepalive | 整型数字 | 非必填/无默认值 | False | 最小值: 0; | \n|forbidden_page_status_code | 整型数字 | 非必填/无默认值 | False | 最小值: 100; 最大值: 999 | \n|ssl_protocols | 列表 | 非必填/无默认值 | False | - 详见下方表格 | \n|ssl_ciphers | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|proxy_ssl_protocols | 列表 | 非必填/无默认值 | False | - 详见下方表格 | \n|proxy_ssl_ciphers | 字符串 | 非必填/无默认值 | False | 最小长度: 0; | \n|x_forwarded_for_option | 指定选项 | 非必填/无默认值 | False | 选项是: ['trust', 'cover', 'append'] | \n\nssl_protocols\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 指定选项 | 必填 | False | 选项是: ['SSLv2', 'SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'] | \n\n\nproxy_ssl_protocols\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 指定选项 | 必填 | False | 选项是: ['SSLv2', 'SSLv3', 'TLSv1', 'TLSv1.1', 'TLSv1.2'] | \n\n", 46 | "/api/v1/conf2/site_proxy_header:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|site_id | 整型数字 | 必填 | False | - | \n|x_forwarded_port | 布尔 | 非必填/无默认值 | False | - | \n|x_forwarded_proto | 布尔 | 非必填/无默认值 | False | - | \n|x_real_ip | 布尔 | 非必填/无默认值 | False | - | \n\n", 47 | "/api/v1/conf2/site/url_prefix:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|session_method_type | 指定选项 | 非必填/无默认值 | False | 选项是: ['re', 'cookie', 'url_query', 'body_form', 'header', 'safeline_session'] | \n|session_method_param | 字符串 | 非必填/无默认值 | False | - | \n|session_method_pattern | 字符串 | 非必填/无默认值 | False | - | \n|url_prefix | 字符串 | 必填 | False | 最大长度: 128; | \n|policy_group | 整型数字 | 必填 | False | - | \n|site_id | 整型数字 | 必填 | False | - | \n\n", 48 | "/api/v1/conf2/site/url_prefix:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|session_method_type | 指定选项 | 非必填/无默认值 | False | 选项是: ['re', 'cookie', 'url_query', 'body_form', 'header', 'safeline_session'] | \n|session_method_param | 字符串 | 非必填/无默认值 | False | - | \n|session_method_pattern | 字符串 | 非必填/无默认值 | False | - | \n|url_prefix | 字符串 | 必填 | False | 最大长度: 128; | \n|policy_group | 整型数字 | 必填 | False | - | \n|id | 整型数字 | 必填 | False | - | \n\n", 49 | "/api/v1/management/curl:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|url | 字符串 | 必填 | False | - | \n\n", 50 | "/api/v1/management/detect_replay:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|target | 字符串 | 必填 | False | - | \n|port | 整型数字 | 必填 | False | 最小值: 1; 最大值: 65535 | \n|protocol | 指定选项 | 必填 | False | 选项是: ['http', 'https'] | \n|content | 字符串 | 必填 | False | - | \n\n", 51 | "/api/v1/management/nslookup:POST": "数据格式\n\n> dns 不填就使用系统的dns\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | 最大长度: 128; | \n|dns | 字符串 | 非必填/'' | False | 最小长度: 0; | \n\n", 52 | "/api/v1/management/ntp_config:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|enabled | 布尔 | 必填 | False | - | \n|servers | 列表 | 非必填/ | False | - 详见下方表格 | \n\nservers\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | - | \n\n", 53 | "/api/v1/management/ping:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | 最大长度: 128; | \n\n", 54 | "/api/v1/management/snmp_config:PUT": "数据格式\n\n> v1 / v2c 协议只需要 community,\n v3 协议只不需要 community\n disable-default-check\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|version | 指定选项 | 必填 | False | 选项是: ['v1v2c', 'v3'] | \n|community | 字符串 | 非必填/无默认值 | False | 最大长度: 24; | \n|username | 字符串 | 非必填/无默认值 | False | 最大长度: 24; | \n|hash_method | 指定选项 | 非必填/无默认值 | False | 选项是: ['MD5', 'SHA'] | \n|password | 字符串 | 非必填/无默认值 | False | 最大长度: 24; 最小长度: 8; | \n|encryption_method | 指定选项 | 非必填/无默认值 | False | 选项是: ['AES', 'DES'] | \n\n", 55 | "/api/v1/management/safeline_config/export:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|smtp_config | 布尔 | 非必填/False | False | - | \n|ip_source | 布尔 | 非必填/False | False | - | \n|waf_resolver | 布尔 | 非必填/False | False | - | \n|cloud_analytics | 布尔 | 非必填/False | False | - | \n|global_waf_state | 布尔 | 非必填/False | False | - | \n|log_ip_source | 布尔 | 非必填/False | False | - | \n|sys_log_config | 布尔 | 非必填/False | False | - | \n|alarm_config | 布尔 | 非必填/False | False | - | \n|certs | 布尔 | 非必填/False | False | - | \n|backends | 布尔 | 非必填/False | False | - | \n|policy | 布尔 | 非必填/False | False | - | \n|acl_rule_template | 布尔 | 非必填/False | False | - | \n|acl_rule | 布尔 | 非必填/False | False | - | \n|acl_cidr_white_list | 布尔 | 非必填/False | False | - | \n\n", 56 | "/api/v1/management/sync_time:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time | 整型数字 | 非必填/0 | False | 最小值: 0; | \n|timezone | 字符串 | 必填 | False | - | \n\n", 57 | "/api/v1/management/traceroute:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|hostname | 字符串 | 必填 | False | 最大长度: 128; | \n\n", 58 | "/api/v1/mario/plugin:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|name | 字符串 | 必填 | False | 最大长度: 64; | \n|comment | 字符串 | 非必填/'' | False | 最小长度: 0; | \n|state | 指定选项 | 必填 | False | 选项是: ['enabled', 'disabled'] | \n|code | 字符串 | 必填 | False | lua 脚本; | \n\n", 59 | "/api/v1/mario/plugin:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | UUID | 必填 | False | UUID4 | \n\n", 60 | "/api/v1/mario/plugin:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|name | 字符串 | 必填 | False | 最大长度: 64; | \n|comment | 字符串 | 非必填/'' | False | 最小长度: 0; | \n|state | 指定选项 | 必填 | False | 选项是: ['enabled', 'disabled'] | \n|code | 字符串 | 必填 | False | lua 脚本; | \n|id | UUID | 必填 | False | UUID4 | \n\n", 61 | "/api/v1/mario/plugin/log:DELETE": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|plugin_id | 字符串 | 必填 | False | - | \n|tag | 字符串 | 非必填/无默认值 | False | - | \n\n", 62 | "/api/v1/mario/plugin/log:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|plugin_id | 字符串 | 必填 | False | - | \n|tag | 字符串 | 非必填/无默认值 | False | - | \n\n", 63 | "/api/v1/mario/plugin/log/download:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|plugin_id | 字符串 | 必填 | False | - | \n|tag | 字符串 | 非必填/无默认值 | False | - | \n\n", 64 | "/api/v1/mario/plugin/state:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|timestamp | 浮点数字 | 必填 | False | - | \n|id | UUID | 必填 | False | UUID4 | \n|hash | 字符串 | 必填 | False | 最大长度: 32; | \n|state | 字符串 | 必填 | False | 最大长度: 32; | \n|total_execution | 整型数字 | 必填 | False | - | \n|total_error_execution | 整型数字 | 必填 | False | - | \n|execution_per_second | 整型数字 | 必填 | False | - | \n|error_execution_per_second | 整型数字 | 必填 | False | - | \n|average_execution_time | 浮点数字 | 必填 | False | - | \n\n", 65 | "/api/v1/mario/plugin/state/log:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|timestamp | 浮点数字 | 必填 | False | - | \n|id | UUID | 必填 | False | UUID4 | \n|tag | 字符串 | 必填 | False | 最大长度: 32; | \n|msg | 字符串 | 必填 | False | - | \n\n", 66 | "/api/v1/stat/risk_level:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|domain | 字符串 | 非必填/无默认值 | False | - | \n\n", 67 | "/api/v1/stat/attack_number_trend:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n|domain | 字符串 | 非必填/无默认值 | False | - | \n\n", 68 | "/api/v1/log/archive/period:PUT": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|date | 指定选项 | 必填 | False | 选项是: ['month', 'week', 'day'] | \n|length | 整型数字 | 必填 | False | 最小值: 1; | \n\n", 69 | "/api/v1/log/delete:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|domain | 字符串 | 非必填/无默认值 | False | - | \n|time_after | 浮点数字 | 非必填/无默认值 | False | - | \n|time_before | 浮点数字 | 非必填/无默认值 | False | - | \n|risk_level | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'low', 'medium', 'high'] | \n|count | 整型数字 | 非必填/无默认值 | False | - | \n|offset | 整型数字 | 非必填/无默认值 | False | - | \n|src_ip | 字符串 | 非必填/无默认值 | False | - | \n|attack_type | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'sql_injection', 'xss', 'csrf', 'ssrf', 'dos', 'backdoor', 'deserialization', 'code_execution', 'code_injection', 'command_injection', 'file_upload', 'file_inclusion', 'redirect', 'weak_permission', 'info_leak', 'unauthorized_access', 'unsafe_config', 'xxe', 'xpath_injection', 'ldap_injection', 'directory_traversal', 'scanner', 'permission_bypass', 'acl_bypass', 'file_write', 'file_download', 'file_deletion', 'logic_error', 'crlf_injection', 'ssti', 'click_hijacking', 'buffer_overflow', 'integer_overflow', 'format_string', 'race_condition', 'timeout', 'unknown'] | \n|action | 指定选项 | 非必填/无默认值 | False | 选项是: ['allow', 'deny'] | \n|event_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | \n|rule_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | \n\n", 70 | "/api/v1/log:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|domain | 字符串 | 非必填/无默认值 | False | - | \n|time_after | 浮点数字 | 非必填/无默认值 | False | - | \n|time_before | 浮点数字 | 非必填/无默认值 | False | - | \n|risk_level | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'low', 'medium', 'high'] | \n|count | 整型数字 | 非必填/无默认值 | False | - | \n|offset | 整型数字 | 非必填/无默认值 | False | - | \n|src_ip | 字符串 | 非必填/无默认值 | False | - | \n|attack_type | 指定选项 | 非必填/无默认值 | False | 选项是: ['none', 'sql_injection', 'xss', 'csrf', 'ssrf', 'dos', 'backdoor', 'deserialization', 'code_execution', 'code_injection', 'command_injection', 'file_upload', 'file_inclusion', 'redirect', 'weak_permission', 'info_leak', 'unauthorized_access', 'unsafe_config', 'xxe', 'xpath_injection', 'ldap_injection', 'directory_traversal', 'scanner', 'permission_bypass', 'acl_bypass', 'file_write', 'file_download', 'file_deletion', 'logic_error', 'crlf_injection', 'ssti', 'click_hijacking', 'buffer_overflow', 'integer_overflow', 'format_string', 'race_condition', 'timeout', 'unknown'] | \n|action | 指定选项 | 非必填/无默认值 | False | 选项是: ['allow', 'deny'] | \n|event_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | \n|rule_id | 字符串 | 非必填/无默认值 | False | 最大长度: 36; | \n\n", 71 | "/api/v1/stat/req_number_trend:GET": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|time_after | 浮点数字 | 非必填/无默认值 | False | 开始时间; 最小值: 0; | \n|time_before | 浮点数字 | 非必填/无默认值 | False | 结束时间; 最小值: 0; | \n\n", 72 | "/api/v1/node/config:POST": "数据格式\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|id | 字符串 | 必填 | False | - | \n|services_status | 字典 | - | - | 详见下面表格 | \n\nservices_status\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|tengine | 字典 | - | - | 详见下面表格 | \n\ntengine\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|version | 整型数字 | 必填 | False | - | \n|status | 字典 | - | - | 详见下面表格 | \n\nstatus\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|status | 字符串 | 必填 | False | - | \n|last_result | 字符串 | 必填 | False | 最小长度: 0; | \n|sites | 字典类型 | - | - | 字典类型 | \n\nsites\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字典 | - | - | 详见下面表格 | \n\n子字段\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|status | 字符串 | 必填 | False | - | \n|upstreams | 字典类型 | - | - | 字典类型 | \n\nupstreams\n\n|字段名|数据类型|是否必填/默认值|NULL|其他|\n|:--|:-:|:-:|:-:|:-:|\n|子字段 | 字符串 | 必填 | False | - | \n\n" 73 | } -------------------------------------------------------------------------------- /series_20/api_doc/account.md: -------------------------------------------------------------------------------- 1 | # /api/LoginAPI 2 | 3 | ## POST 4 | 5 | ### [数据格式定义] 6 | 数据格式 7 | 8 | |字段名|数据类型|是否必填/默认值|NULL|其他| 9 | |:--|:-:|:-:|:-:|:-:| 10 | |tfa_token | 字符串 | 非必填/'' | False | 两步验证验证码; 最小长度: 0; | 11 | |username | 字符串 | 必填 | False | - | 12 | |password | 字符串 | 必填 | False | - | 13 | 14 | ?> 不需要登录 15 | 16 | ### - 密码登录 17 | 18 | 19 | 20 | 请求 21 | 22 | ```js 23 | { 24 | "username": "admin", 25 | "password": "admin@password" 26 | } 27 | ``` 28 | 29 | 响应 30 | 31 | ```js 32 | { 33 | "err": null, 34 | "data": { 35 | "id": 15, 36 | "is_locked": false, 37 | "ip_policy": [], 38 | "last_login": "1544513489", 39 | "username": "admin", 40 | "comment": "", 41 | "tfa_enabled": false, 42 | "create_time": "1544513489", 43 | "last_update_time": "1544513489", 44 | "last_login_time": "1544513489", 45 | "last_change_password_time": "1544513489", 46 | "permissions": [ 47 | "System Monitor", 48 | "System Management", 49 | "User Management", 50 | "Website And Security Policy Management", 51 | "Audit Log Management" 52 | ], 53 | "authentication_method": "password", 54 | "session_timeout_time": 172800 55 | }, 56 | "msg": null 57 | } 58 | ``` 59 | 60 | ### - 使用两步验证登录,验证通过 61 | 62 | 63 | 64 | 请求 65 | 66 | ```js 67 | { 68 | "username": "admin", 69 | "password": "admin@password", 70 | "tfa_token": "foobar" 71 | } 72 | ``` 73 | 74 | 响应 75 | 76 | ```js 77 | { 78 | "err": null, 79 | "data": { 80 | "id": 18, 81 | "is_locked": false, 82 | "ip_policy": [], 83 | "last_login": "1544513491", 84 | "username": "admin", 85 | "comment": "", 86 | "tfa_enabled": true, 87 | "create_time": "1544513490", 88 | "last_update_time": "1544513490", 89 | "last_login_time": "1544513491", 90 | "last_change_password_time": "1544513490", 91 | "permissions": [ 92 | "System Monitor", 93 | "System Management", 94 | "User Management", 95 | "Website And Security Policy Management", 96 | "Audit Log Management" 97 | ], 98 | "authentication_method": "password", 99 | "session_timeout_time": 172800 100 | }, 101 | "msg": null 102 | } 103 | ``` 104 | 105 | ### - 密码错误 106 | 107 | 108 | 109 | 请求 110 | 111 | ```js 112 | { 113 | "username": "admin", 114 | "password": "admin@passwordfoobar" 115 | } 116 | ``` 117 | 118 | 响应 119 | 120 | ```js 121 | { 122 | "err": "error", 123 | "msg": "Incorrect username or password" 124 | } 125 | ``` 126 | 127 | ### - 没有输入两步验证验证码 128 | 129 | 130 | 131 | 请求 132 | 133 | ```js 134 | { 135 | "username": "admin", 136 | "password": "admin@password" 137 | } 138 | ``` 139 | 140 | 响应 141 | 142 | ```js 143 | { 144 | "err": "tfa-required", 145 | "msg": "Please input two factor auth token" 146 | } 147 | ``` 148 | 149 | # /api/CertLoginAPI 150 | 151 | ## GET 152 | 153 | ?> 不需要登录 154 | 155 | ### - 检查是否安装了证书,证书是否有效 156 | 157 | 158 | 159 | 响应 160 | 161 | ```js 162 | { 163 | "err": null, 164 | "data": { 165 | "result": true 166 | }, 167 | "msg": null 168 | } 169 | ``` 170 | 171 | ## POST 172 | 173 | ### [数据格式定义] 174 | 数据格式 175 | 176 | |字段名|数据类型|是否必填/默认值|NULL|其他| 177 | |:--|:-:|:-:|:-:|:-:| 178 | |tfa_token | 字符串 | 非必填/'' | False | 两步验证验证码; 最小长度: 0; | 179 | 180 | ?> 不需要登录 181 | 182 | ### - 正常登录 183 | 184 | 185 | 186 | 请求 187 | 188 | ```js 189 | {} 190 | ``` 191 | 192 | 响应 193 | 194 | ```js 195 | { 196 | "err": null, 197 | "data": { 198 | "id": 3, 199 | "is_locked": false, 200 | "ip_policy": [], 201 | "last_login": "1544513479", 202 | "username": "admin", 203 | "comment": "", 204 | "tfa_enabled": false, 205 | "create_time": "1544513479", 206 | "last_update_time": "1544513479", 207 | "last_login_time": "1544513479", 208 | "last_change_password_time": "1544513479", 209 | "permissions": [ 210 | "System Monitor", 211 | "System Management", 212 | "User Management", 213 | "Website And Security Policy Management", 214 | "Audit Log Management" 215 | ], 216 | "authentication_method": "cert", 217 | "session_timeout_time": 172800 218 | }, 219 | "msg": null 220 | } 221 | ``` 222 | 223 | ### - 使用两步验证登录,验证通过 224 | 225 | 226 | 227 | 请求 228 | 229 | ```js 230 | { 231 | "tfa_token": "foobar" 232 | } 233 | ``` 234 | 235 | 响应 236 | 237 | ```js 238 | { 239 | "err": null, 240 | "data": { 241 | "id": 4, 242 | "is_locked": false, 243 | "ip_policy": [], 244 | "last_login": "1544513480", 245 | "username": "admin", 246 | "comment": "", 247 | "tfa_enabled": true, 248 | "create_time": "1544513479", 249 | "last_update_time": "1544513479", 250 | "last_login_time": "1544513480", 251 | "last_change_password_time": "1544513479", 252 | "permissions": [ 253 | "System Monitor", 254 | "System Management", 255 | "User Management", 256 | "Website And Security Policy Management", 257 | "Audit Log Management" 258 | ], 259 | "authentication_method": "cert", 260 | "session_timeout_time": 172800 261 | }, 262 | "msg": null 263 | } 264 | ``` 265 | 266 | # /api/ResetUserCredentialAPI 267 | 268 | ## PUT 269 | 270 | ### [数据格式定义] 271 | 数据格式 272 | 273 | > 只需要传递 ID 274 | 275 | |字段名|数据类型|是否必填/默认值|NULL|其他| 276 | |:--|:-:|:-:|:-:|:-:| 277 | |id | 整型数字 | 必填 | False | - | 278 | 279 | ### 权限 280 | - User Management 281 | 282 | ### - 管理员重置用户证书 283 | 284 | 285 | 286 | 请求 287 | 288 | ```js 289 | { 290 | "id": 23 291 | } 292 | ``` 293 | 294 | 响应 295 | 296 | ```js 297 | { 298 | "err": null, 299 | "data": { 300 | "user": { 301 | "id": 23, 302 | "is_locked": false, 303 | "ip_policy": [], 304 | "last_login": null, 305 | "username": "test", 306 | "comment": "", 307 | "tfa_enabled": false, 308 | "create_time": "1544513493", 309 | "last_update_time": "1544513493", 310 | "last_login_time": null, 311 | "last_change_password_time": "1544513493", 312 | "permissions": [ 313 | "System Monitor", 314 | "System Management", 315 | "User Management", 316 | "Website And Security Policy Management", 317 | "Audit Log Management" 318 | ], 319 | "authentication_method": "cert", 320 | "session_timeout_time": 172800 321 | }, 322 | "extra": { 323 | "token": "2081824916eaa700b5c9006ffa3697fb:1gWcVy:Dqg8yXQNexzNVjVL0HfenYURwOI", 324 | "password": "m7ezSC" 325 | } 326 | }, 327 | "msg": null 328 | } 329 | ``` 330 | 331 | ### - 管理员重置用户密码 332 | 333 | 334 | 335 | 请求 336 | 337 | ```js 338 | { 339 | "id": 25 340 | } 341 | ``` 342 | 343 | 响应 344 | 345 | ```js 346 | { 347 | "err": null, 348 | "data": { 349 | "user": { 350 | "id": 25, 351 | "is_locked": false, 352 | "ip_policy": [], 353 | "last_login": null, 354 | "username": "test", 355 | "comment": "", 356 | "tfa_enabled": false, 357 | "create_time": "1544513494", 358 | "last_update_time": "1544513494", 359 | "last_login_time": null, 360 | "last_change_password_time": "1544513494", 361 | "permissions": [ 362 | "System Monitor", 363 | "System Management", 364 | "User Management", 365 | "Website And Security Policy Management", 366 | "Audit Log Management" 367 | ], 368 | "authentication_method": "password", 369 | "session_timeout_time": 172800 370 | }, 371 | "extra": { 372 | "password": "v4DN0m" 373 | } 374 | }, 375 | "msg": null 376 | } 377 | ``` 378 | 379 | # /api/ChangeUserCredentialAPI 380 | 381 | ## PUT 382 | 383 | ### [数据格式定义] 384 | 数据格式 385 | 386 | |字段名|数据类型|是否必填/默认值|NULL|其他| 387 | |:--|:-:|:-:|:-:|:-:| 388 | |authentication_method | 指定选项 | 必填 | False | 选项是: ['ldap', 'password', 'cert'] | 389 | |old_password | 字符串 | 非必填/'' | False | 最小长度: 0; | 390 | |new_password | 字符串 | 非必填/'' | False | 最小长度: 0; | 391 | |tfa_token | 字符串 | 非必填/'' | False | 最小长度: 0; | 392 | 393 | 394 | 395 | ### - 用户重新生成证书 396 | 397 | 398 | 399 | 请求 400 | 401 | ```js 402 | { 403 | "authentication_method": "cert" 404 | } 405 | ``` 406 | 407 | 响应 408 | 409 | ```js 410 | { 411 | "err": null, 412 | "data": { 413 | "user": { 414 | "id": 6, 415 | "is_locked": false, 416 | "ip_policy": [], 417 | "last_login": "1544513482", 418 | "username": "admin", 419 | "comment": "", 420 | "tfa_enabled": false, 421 | "create_time": "1544513480", 422 | "last_update_time": "1544513480", 423 | "last_login_time": "1544513482", 424 | "last_change_password_time": "1544513480", 425 | "permissions": [ 426 | "System Monitor", 427 | "System Management", 428 | "User Management", 429 | "Website And Security Policy Management", 430 | "Audit Log Management" 431 | ], 432 | "authentication_method": "cert", 433 | "session_timeout_time": 172800 434 | }, 435 | "extra": { 436 | "token": "a8aee7711c0c811d230c1ed52cc9e658:1gWcVm:hPP-lCq5AhY6xj0tf22xCoAZv9I", 437 | "password": "EHhhF8" 438 | } 439 | }, 440 | "msg": null 441 | } 442 | ``` 443 | 444 | ### - 证书登录切换到密码登录 445 | 446 | 447 | 448 | 请求 449 | 450 | ```js 451 | { 452 | "authentication_method": "password", 453 | "new_password": "somepassword@password" 454 | } 455 | ``` 456 | 457 | 响应 458 | 459 | ```js 460 | { 461 | "err": null, 462 | "data": { 463 | "user": { 464 | "id": 7, 465 | "is_locked": false, 466 | "ip_policy": [], 467 | "last_login": "1544513483", 468 | "username": "admin", 469 | "comment": "", 470 | "tfa_enabled": false, 471 | "create_time": "1544513482", 472 | "last_update_time": "1544513482", 473 | "last_login_time": "1544513483", 474 | "last_change_password_time": "1544513484", 475 | "permissions": [ 476 | "System Monitor", 477 | "System Management", 478 | "User Management", 479 | "Website And Security Policy Management", 480 | "Audit Log Management" 481 | ], 482 | "authentication_method": "password", 483 | "session_timeout_time": 172800 484 | }, 485 | "extra": {} 486 | }, 487 | "msg": null 488 | } 489 | ``` 490 | 491 | ### - 密码登录切换到证书登录 492 | 493 | 494 | 495 | 请求 496 | 497 | ```js 498 | { 499 | "authentication_method": "cert", 500 | "old_password": "testadmin@password" 501 | } 502 | ``` 503 | 504 | 响应 505 | 506 | ```js 507 | { 508 | "err": null, 509 | "data": { 510 | "user": { 511 | "id": 8, 512 | "is_locked": false, 513 | "ip_policy": [], 514 | "last_login": "1544513484", 515 | "username": "admin", 516 | "comment": "", 517 | "tfa_enabled": false, 518 | "create_time": "1544513484", 519 | "last_update_time": "1544513484", 520 | "last_login_time": null, 521 | "last_change_password_time": "1544513484", 522 | "permissions": [ 523 | "System Monitor", 524 | "System Management", 525 | "User Management", 526 | "Website And Security Policy Management", 527 | "Audit Log Management" 528 | ], 529 | "authentication_method": "cert", 530 | "session_timeout_time": 172800 531 | }, 532 | "extra": { 533 | "token": "2ccd35345fbcba53d23e227dd8b90975:1gWcVp:4Cl9dl3sqW-km38RI_jcZ0ziP4I", 534 | "password": "IqsveW" 535 | } 536 | }, 537 | "msg": null 538 | } 539 | ``` 540 | 541 | ### - 用户修改密码 542 | 543 | 544 | 545 | 请求 546 | 547 | ```js 548 | { 549 | "authentication_method": "password", 550 | "old_password": "testadmin@password", 551 | "new_password": "somerandom@password" 552 | } 553 | ``` 554 | 555 | 响应 556 | 557 | ```js 558 | { 559 | "err": null, 560 | "data": { 561 | "user": { 562 | "id": 9, 563 | "is_locked": false, 564 | "ip_policy": [], 565 | "last_login": "1544513485", 566 | "username": "admin", 567 | "comment": "", 568 | "tfa_enabled": false, 569 | "create_time": "1544513485", 570 | "last_update_time": "1544513485", 571 | "last_login_time": null, 572 | "last_change_password_time": "1544513486", 573 | "permissions": [ 574 | "System Monitor", 575 | "System Management", 576 | "User Management", 577 | "Website And Security Policy Management", 578 | "Audit Log Management" 579 | ], 580 | "authentication_method": "password", 581 | "session_timeout_time": 172800 582 | }, 583 | "extra": {} 584 | }, 585 | "msg": null 586 | } 587 | ``` 588 | 589 | # /api/UserAPITokenAPI 590 | 591 | ## GET 592 | 593 | 594 | 595 | ### - 获取所有的 api token 和描述 596 | 597 | 598 | 599 | ?> 本 API 支持翻页参数 600 | 601 | 响应 602 | 603 | ```js 604 | { 605 | "err": null, 606 | "data": [ 607 | { 608 | "id": 4, 609 | "value": "TlYBuIwm********************************", 610 | "ip_policy": [ 611 | "0.0.0.0/0", 612 | "::/0" 613 | ], 614 | "name": "foobar", 615 | "create_time": "1544513503", 616 | "is_internal": false, 617 | "permissions": [ 618 | "User Management" 619 | ], 620 | "expire_time": "1544513603" 621 | } 622 | ], 623 | "msg": null 624 | } 625 | ``` 626 | 627 | ## POST 628 | 629 | ### [数据格式定义] 630 | 数据格式 631 | 632 | |字段名|数据类型|是否必填/默认值|NULL|其他| 633 | |:--|:-:|:-:|:-:|:-:| 634 | |name | 字符串 | 必填 | False | - | 635 | |expire_time | 浮点数字 | 非必填/None | True | 为 null 时, 表示永不过期; | 636 | |permissions | 指定选项 | 必填 | False | 选项是: ['System Monitor', 'System Management', 'User Management', 'Website And Security Policy Management', 'Audit Log Management'] | 637 | |ip_policy | 列表 | 必填 | False | [ - 详见下方表格](#nfshkhytihjvuqxtekzhqwrhqsvmnfvz) | 638 | 639 | ip_policy 640 | 641 | |字段名|数据类型|是否必填/默认值|NULL|其他| 642 | |:--|:-:|:-:|:-:|:-:| 643 | |子字段 | 字符串 | 必填 | False | IP 范围; | 644 | 645 | 646 | 647 | ### - 创建 api token 648 | 649 | 650 | 651 | 请求 652 | 653 | ```js 654 | { 655 | "name": "foobar", 656 | "expire_time": 1544513602.0145867, 657 | "permissions": [ 658 | "User Management" 659 | ], 660 | "ip_policy": [ 661 | "0.0.0.0/0", 662 | "::/0" 663 | ] 664 | } 665 | ``` 666 | 667 | 响应 668 | 669 | ```js 670 | { 671 | "err": null, 672 | "data": { 673 | "id": 1, 674 | "value": "rQMxtSfheOlswkE8BBxRhFpP2J7nHCLjCIAq1crv", 675 | "ip_policy": [ 676 | "0.0.0.0/0", 677 | "::/0" 678 | ], 679 | "name": "foobar", 680 | "create_time": "1544513502", 681 | "is_internal": false, 682 | "permissions": [ 683 | "User Management" 684 | ], 685 | "expire_time": "1544513602" 686 | }, 687 | "msg": null 688 | } 689 | ``` 690 | 691 | ## PUT 692 | 693 | ### [数据格式定义] 694 | 数据格式 695 | 696 | |字段名|数据类型|是否必填/默认值|NULL|其他| 697 | |:--|:-:|:-:|:-:|:-:| 698 | |id | 整型数字 | 必填 | False | - | 699 | |permissions | 指定选项 | 必填 | False | 选项是: ['System Monitor', 'System Management', 'User Management', 'Website And Security Policy Management', 'Audit Log Management'] | 700 | |ip_policy | 列表 | 必填 | False | [ - 详见下方表格](#acvnlzcwhptxgopdwmlxmulkpovsfsvp) | 701 | 702 | ip_policy 703 | 704 | |字段名|数据类型|是否必填/默认值|NULL|其他| 705 | |:--|:-:|:-:|:-:|:-:| 706 | |子字段 | 字符串 | 必填 | False | IP 范围; | 707 | 708 | 709 | 710 | ### - 编辑 api token 的 permission 和 ip_policy 711 | 712 | 713 | 714 | 请求 715 | 716 | ```js 717 | { 718 | "id": 3, 719 | "permissions": [ 720 | "User Management", 721 | "System Management" 722 | ], 723 | "ip_policy": [ 724 | "0.0.0.0/0", 725 | "::/0", 726 | "10.0.0.0/24" 727 | ] 728 | } 729 | ``` 730 | 731 | 响应 732 | 733 | ```js 734 | { 735 | "err": null, 736 | "data": { 737 | "id": 3, 738 | "value": "KqKNX0CA********************************", 739 | "ip_policy": [ 740 | "0.0.0.0/0", 741 | "::/0", 742 | "10.0.0.0/24" 743 | ], 744 | "name": "foobar", 745 | "create_time": "1544513502", 746 | "is_internal": false, 747 | "permissions": [ 748 | "System Management", 749 | "User Management" 750 | ], 751 | "expire_time": "1544513602" 752 | }, 753 | "msg": null 754 | } 755 | ``` 756 | 757 | ## DELETE 758 | 759 | ### [数据格式定义] 760 | 数据格式 761 | 762 | > 只需要传递 ID 763 | 764 | |字段名|数据类型|是否必填/默认值|NULL|其他| 765 | |:--|:-:|:-:|:-:|:-:| 766 | |id | 整型数字 | 必填 | False | - | 767 | 768 | 769 | 770 | ### - 删除 api token 771 | 772 | 773 | 774 | 请求 775 | 776 | ```js 777 | { 778 | "id": 2 779 | } 780 | ``` 781 | 782 | 响应 783 | 784 | ```js 785 | { 786 | "err": null, 787 | "data": null, 788 | "msg": null 789 | } 790 | ``` 791 | 792 | # /api/UserTFAConfigAPI 793 | 794 | ## GET 795 | 796 | 797 | 798 | ### - 获取验证码链接,前端需要生成二维码,然后使用 app 扫描添加 799 | 800 | 801 | 802 | 响应 803 | 804 | ```js 805 | { 806 | "err": null, 807 | "data": { 808 | "uri": "otpauth://totp/SafeLine:admin%40SafeLine?secret=ZXMDABN2HIAQK32G&issuer=SafeLine" 809 | }, 810 | "msg": null 811 | } 812 | ``` 813 | 814 | ## POST 815 | 816 | ### [数据格式定义] 817 | 数据格式 818 | 819 | |字段名|数据类型|是否必填/默认值|NULL|其他| 820 | |:--|:-:|:-:|:-:|:-:| 821 | |tfa_token | 字符串 | 非必填/'' | False | 两步验证验证码; 最小长度: 0; | 822 | |action | 指定选项 | 必填 | False | 选项是: ['disable', 'enable'] | 823 | 824 | 825 | 826 | ### - 禁用 tfa,需要输入一个 app 上生成的验证码 827 | 828 | 829 | 830 | 请求 831 | 832 | ```js 833 | {} 834 | ``` 835 | 836 | 响应 837 | 838 | ```js 839 | { 840 | "err": null, 841 | "data": null, 842 | "msg": null 843 | } 844 | ``` 845 | 846 | ### - 启用 tfa,需要输入一个 app 上生成的验证码 847 | 848 | 849 | 850 | 请求 851 | 852 | ```js 853 | {} 854 | ``` 855 | 856 | 响应 857 | 858 | ```js 859 | { 860 | "err": null, 861 | "data": null, 862 | "msg": null 863 | } 864 | ``` 865 | 866 | # /api/UserAPI 867 | 868 | ## GET 869 | 870 | ### [数据格式定义] 871 | 数据格式 872 | 873 | |字段名|数据类型|是否必填/默认值|NULL|其他| 874 | |:--|:-:|:-:|:-:|:-:| 875 | |id__in | 列表 | 非必填/ | False | [ - 详见下方表格](#vardiqgkkrqfynqvaiztxzamrkfhynih) | 876 | |username | 列表 | 必填 | False | [ - 详见下方表格](#ykwppezutgglekopzilqzpqyrsurvadn) | 877 | 878 | id__in 879 | 880 | |字段名|数据类型|是否必填/默认值|NULL|其他| 881 | |:--|:-:|:-:|:-:|:-:| 882 | |子字段 | 整型数字 | 必填 | False | - | 883 | 884 | 885 | username 886 | 887 | |字段名|数据类型|是否必填/默认值|NULL|其他| 888 | |:--|:-:|:-:|:-:|:-:| 889 | |子字段 | 字符串 | 必填 | False | - | 890 | 891 | ### 权限 892 | - User Management 893 | 894 | ### - 根据 id 获取用户信息 895 | 896 | 897 | 898 | 请求 899 | 900 | ```js 901 | { 902 | "id": 41 903 | } 904 | ``` 905 | 906 | 响应 907 | 908 | ```js 909 | { 910 | "err": null, 911 | "data": { 912 | "id": 41, 913 | "is_locked": false, 914 | "ip_policy": [], 915 | "last_login": "1544513500", 916 | "username": "admin", 917 | "comment": "", 918 | "tfa_enabled": false, 919 | "create_time": "1544513500", 920 | "last_update_time": "1544513500", 921 | "last_login_time": null, 922 | "last_change_password_time": "1544513500", 923 | "permissions": [ 924 | "System Monitor", 925 | "System Management", 926 | "User Management", 927 | "Website And Security Policy Management", 928 | "Audit Log Management" 929 | ], 930 | "authentication_method": "password", 931 | "session_timeout_time": 172800 932 | }, 933 | "msg": null 934 | } 935 | ``` 936 | 937 | ### - 根据 username 获取用户信息 938 | 939 | 940 | 941 | ?> 本 API 支持翻页参数 942 | 943 | 请求 944 | 945 | ```js 946 | { 947 | "username": "admin" 948 | } 949 | ``` 950 | 951 | 响应 952 | 953 | ```js 954 | { 955 | "err": null, 956 | "data": [ 957 | { 958 | "id": 42, 959 | "is_locked": false, 960 | "ip_policy": [], 961 | "last_login": "1544513500", 962 | "username": "admin", 963 | "comment": "", 964 | "tfa_enabled": false, 965 | "create_time": "1544513500", 966 | "last_update_time": "1544513500", 967 | "last_login_time": null, 968 | "last_change_password_time": "1544513500", 969 | "permissions": [ 970 | "System Monitor", 971 | "System Management", 972 | "User Management", 973 | "Website And Security Policy Management", 974 | "Audit Log Management" 975 | ], 976 | "authentication_method": "password", 977 | "session_timeout_time": 172800 978 | } 979 | ], 980 | "msg": null 981 | } 982 | ``` 983 | 984 | ### - 获取用户列表 985 | 986 | 987 | 988 | ?> 本 API 支持翻页参数 989 | 990 | 响应 991 | 992 | ```js 993 | { 994 | "err": null, 995 | "data": [ 996 | { 997 | "id": 43, 998 | "is_locked": false, 999 | "ip_policy": [], 1000 | "last_login": "1544513501", 1001 | "username": "admin", 1002 | "comment": "", 1003 | "tfa_enabled": false, 1004 | "create_time": "1544513500", 1005 | "last_update_time": "1544513500", 1006 | "last_login_time": null, 1007 | "last_change_password_time": "1544513500", 1008 | "permissions": [ 1009 | "System Monitor", 1010 | "System Management", 1011 | "User Management", 1012 | "Website And Security Policy Management", 1013 | "Audit Log Management" 1014 | ], 1015 | "authentication_method": "password", 1016 | "session_timeout_time": 172800 1017 | } 1018 | ], 1019 | "msg": null 1020 | } 1021 | ``` 1022 | 1023 | ## POST 1024 | 1025 | ### [数据格式定义] 1026 | 数据格式 1027 | 1028 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1029 | |:--|:-:|:-:|:-:|:-:| 1030 | |username | 字符串 | 必填 | False | 最大长度: 16; | 1031 | |comment | 字符串 | 必填 | False | 最小长度: 0; | 1032 | |authentication_method | 指定选项 | 必填 | False | 选项是: ['ldap', 'password', 'cert'] | 1033 | |permissions | 指定选项 | 必填 | False | 选项是: ['System Monitor', 'System Management', 'User Management', 'Website And Security Policy Management', 'Audit Log Management'] | 1034 | |ip_policy | 列表 | 非必填/ | False | [ - 详见下方表格](#vfxpzftwbemywuozrruxdlhqolvxomia) | 1035 | 1036 | ip_policy 1037 | 1038 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1039 | |:--|:-:|:-:|:-:|:-:| 1040 | |子字段 | 字符串 | 必填 | False | IP 范围; | 1041 | 1042 | ### 权限 1043 | - User Management 1044 | 1045 | ### - 创建证书认证的用户 1046 | 1047 | extra 中 password 是证书的密码,token 是证书下载用的,见证书下载 api 1048 | 1049 | 请求 1050 | 1051 | ```js 1052 | { 1053 | "username": "foobar", 1054 | "comment": "test", 1055 | "authentication_method": "cert", 1056 | "permissions": [ 1057 | "System Monitor", 1058 | "System Management", 1059 | "User Management", 1060 | "Website And Security Policy Management", 1061 | "Audit Log Management" 1062 | ], 1063 | "ip_policy": [] 1064 | } 1065 | ``` 1066 | 1067 | 响应 1068 | 1069 | ```js 1070 | { 1071 | "err": null, 1072 | "data": { 1073 | "user": { 1074 | "id": 28, 1075 | "is_locked": false, 1076 | "ip_policy": [], 1077 | "last_login": null, 1078 | "username": "foobar", 1079 | "comment": "test", 1080 | "tfa_enabled": false, 1081 | "create_time": "1544513495", 1082 | "last_update_time": "1544513495", 1083 | "last_login_time": null, 1084 | "last_change_password_time": null, 1085 | "permissions": [ 1086 | "System Management", 1087 | "User Management", 1088 | "Website And Security Policy Management", 1089 | "System Monitor", 1090 | "Audit Log Management" 1091 | ], 1092 | "authentication_method": "cert", 1093 | "session_timeout_time": 172800 1094 | }, 1095 | "extra": { 1096 | "token": "3857990760d7b0399ddd8efdb4e64cf8:1gWcW0:PDTaeMnNXjzPo7sErKMtpdtvRnE", 1097 | "password": "H6Gc1X" 1098 | } 1099 | }, 1100 | "msg": null 1101 | } 1102 | ``` 1103 | 1104 | ### - 创建 ldap 认证的用户 1105 | 1106 | 1107 | 1108 | 请求 1109 | 1110 | ```js 1111 | { 1112 | "username": "foobar", 1113 | "comment": "test", 1114 | "authentication_method": "ldap", 1115 | "permissions": [ 1116 | "System Monitor", 1117 | "System Management", 1118 | "User Management", 1119 | "Website And Security Policy Management", 1120 | "Audit Log Management" 1121 | ], 1122 | "ip_policy": [] 1123 | } 1124 | ``` 1125 | 1126 | 响应 1127 | 1128 | ```js 1129 | { 1130 | "err": null, 1131 | "data": { 1132 | "user": { 1133 | "id": 30, 1134 | "is_locked": false, 1135 | "ip_policy": [], 1136 | "last_login": null, 1137 | "username": "foobar", 1138 | "comment": "test", 1139 | "tfa_enabled": false, 1140 | "create_time": "1544513496", 1141 | "last_update_time": "1544513496", 1142 | "last_login_time": null, 1143 | "last_change_password_time": null, 1144 | "permissions": [ 1145 | "System Management", 1146 | "User Management", 1147 | "Website And Security Policy Management", 1148 | "System Monitor", 1149 | "Audit Log Management" 1150 | ], 1151 | "authentication_method": "ldap", 1152 | "session_timeout_time": 172800 1153 | }, 1154 | "extra": null 1155 | }, 1156 | "msg": null 1157 | } 1158 | ``` 1159 | 1160 | ### - 创建密码认证的用户 1161 | 1162 | 密码是随机生成的 1163 | 1164 | 请求 1165 | 1166 | ```js 1167 | { 1168 | "username": "foobar", 1169 | "comment": "test", 1170 | "authentication_method": "password", 1171 | "permissions": [ 1172 | "System Monitor", 1173 | "System Management", 1174 | "User Management", 1175 | "Website And Security Policy Management", 1176 | "Audit Log Management" 1177 | ], 1178 | "ip_policy": [] 1179 | } 1180 | ``` 1181 | 1182 | 响应 1183 | 1184 | ```js 1185 | { 1186 | "err": null, 1187 | "data": { 1188 | "user": { 1189 | "id": 32, 1190 | "is_locked": false, 1191 | "ip_policy": [], 1192 | "last_login": null, 1193 | "username": "foobar", 1194 | "comment": "test", 1195 | "tfa_enabled": false, 1196 | "create_time": "1544513497", 1197 | "last_update_time": "1544513497", 1198 | "last_login_time": null, 1199 | "last_change_password_time": "1544513497", 1200 | "permissions": [ 1201 | "System Management", 1202 | "User Management", 1203 | "Website And Security Policy Management", 1204 | "System Monitor", 1205 | "Audit Log Management" 1206 | ], 1207 | "authentication_method": "password", 1208 | "session_timeout_time": 172800 1209 | }, 1210 | "extra": { 1211 | "password": "eRFfGO" 1212 | } 1213 | }, 1214 | "msg": null 1215 | } 1216 | ``` 1217 | 1218 | ## PUT 1219 | 1220 | ### [数据格式定义] 1221 | 数据格式 1222 | 1223 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1224 | |:--|:-:|:-:|:-:|:-:| 1225 | |id | 整型数字 | 必填 | False | - | 1226 | |comment | 字符串 | 必填 | False | 最小长度: 0; | 1227 | |authentication_method | 指定选项 | 必填 | False | 选项是: ['ldap', 'password', 'cert'] | 1228 | |permissions | 指定选项 | 必填 | False | 选项是: ['System Monitor', 'System Management', 'User Management', 'Website And Security Policy Management', 'Audit Log Management'] | 1229 | |ip_policy | 列表 | 必填 | False | [ - 详见下方表格](#ywomadrftelbefzwypfqmnxaoghbvzkp) | 1230 | 1231 | ip_policy 1232 | 1233 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1234 | |:--|:-:|:-:|:-:|:-:| 1235 | |子字段 | 字符串 | 必填 | False | IP 范围; | 1236 | 1237 | ### 权限 1238 | - User Management 1239 | 1240 | ### - 编辑用户登录方式 1241 | 1242 | 1243 | 1244 | 请求 1245 | 1246 | ```js 1247 | { 1248 | "id": 36, 1249 | "is_locked": false, 1250 | "ip_policy": [], 1251 | "last_login": null, 1252 | "username": "foobar", 1253 | "comment": "test", 1254 | "tfa_enabled": false, 1255 | "create_time": "1544513498", 1256 | "last_update_time": "1544513498", 1257 | "last_login_time": null, 1258 | "last_change_password_time": "1544513498", 1259 | "permissions": [ 1260 | "System Management", 1261 | "User Management", 1262 | "Website And Security Policy Management", 1263 | "System Monitor", 1264 | "Audit Log Management" 1265 | ], 1266 | "authentication_method": "cert", 1267 | "session_timeout_time": 172800 1268 | } 1269 | ``` 1270 | 1271 | 响应 1272 | 1273 | ```js 1274 | { 1275 | "err": null, 1276 | "data": { 1277 | "user": { 1278 | "id": 36, 1279 | "is_locked": false, 1280 | "ip_policy": [], 1281 | "last_login": null, 1282 | "username": "foobar", 1283 | "comment": "test", 1284 | "tfa_enabled": false, 1285 | "create_time": "1544513498", 1286 | "last_update_time": "1544513498", 1287 | "last_login_time": null, 1288 | "last_change_password_time": "1544513498", 1289 | "permissions": [ 1290 | "System Management", 1291 | "User Management", 1292 | "Website And Security Policy Management", 1293 | "System Monitor", 1294 | "Audit Log Management" 1295 | ], 1296 | "authentication_method": "cert", 1297 | "session_timeout_time": 172800 1298 | }, 1299 | "extra": { 1300 | "token": "b161c788e116639277ee86fd5106d1c3:1gWcW2:xFbNZMmIp54S87_C7fjA9EmCDgk", 1301 | "password": "fZmHt3" 1302 | } 1303 | }, 1304 | "msg": null 1305 | } 1306 | ``` 1307 | 1308 | ### - 编辑用户 IP 策略 1309 | 1310 | 1311 | 1312 | 请求 1313 | 1314 | ```js 1315 | { 1316 | "id": 38, 1317 | "is_locked": false, 1318 | "ip_policy": [ 1319 | "0.0.0.0/0", 1320 | "::/0" 1321 | ], 1322 | "last_login": null, 1323 | "username": "foobar", 1324 | "comment": "test", 1325 | "tfa_enabled": false, 1326 | "create_time": "1544513499", 1327 | "last_update_time": "1544513499", 1328 | "last_login_time": null, 1329 | "last_change_password_time": "1544513499", 1330 | "permissions": [ 1331 | "System Management", 1332 | "User Management", 1333 | "Website And Security Policy Management", 1334 | "System Monitor", 1335 | "Audit Log Management" 1336 | ], 1337 | "authentication_method": "password", 1338 | "session_timeout_time": 172800 1339 | } 1340 | ``` 1341 | 1342 | 响应 1343 | 1344 | ```js 1345 | { 1346 | "err": null, 1347 | "data": { 1348 | "user": { 1349 | "id": 38, 1350 | "is_locked": false, 1351 | "ip_policy": [ 1352 | "0.0.0.0/0", 1353 | "::/0" 1354 | ], 1355 | "last_login": null, 1356 | "username": "foobar", 1357 | "comment": "test", 1358 | "tfa_enabled": false, 1359 | "create_time": "1544513499", 1360 | "last_update_time": "1544513499", 1361 | "last_login_time": null, 1362 | "last_change_password_time": "1544513499", 1363 | "permissions": [ 1364 | "System Management", 1365 | "User Management", 1366 | "Website And Security Policy Management", 1367 | "System Monitor", 1368 | "Audit Log Management" 1369 | ], 1370 | "authentication_method": "password", 1371 | "session_timeout_time": 172800 1372 | }, 1373 | "extra": {} 1374 | }, 1375 | "msg": null 1376 | } 1377 | ``` 1378 | 1379 | ### - 编辑用户权限 1380 | 1381 | 1382 | 1383 | 请求 1384 | 1385 | ```js 1386 | { 1387 | "id": 40, 1388 | "is_locked": false, 1389 | "ip_policy": [], 1390 | "last_login": null, 1391 | "username": "foobar", 1392 | "comment": "test", 1393 | "tfa_enabled": false, 1394 | "create_time": "1544513499", 1395 | "last_update_time": "1544513499", 1396 | "last_login_time": null, 1397 | "last_change_password_time": "1544513499", 1398 | "permissions": [ 1399 | "System Management", 1400 | "User Management", 1401 | "Website And Security Policy Management", 1402 | "Audit Log Management" 1403 | ], 1404 | "authentication_method": "password", 1405 | "session_timeout_time": 172800 1406 | } 1407 | ``` 1408 | 1409 | 响应 1410 | 1411 | ```js 1412 | { 1413 | "err": null, 1414 | "data": { 1415 | "user": { 1416 | "id": 40, 1417 | "is_locked": false, 1418 | "ip_policy": [], 1419 | "last_login": null, 1420 | "username": "foobar", 1421 | "comment": "test", 1422 | "tfa_enabled": false, 1423 | "create_time": "1544513499", 1424 | "last_update_time": "1544513499", 1425 | "last_login_time": null, 1426 | "last_change_password_time": "1544513499", 1427 | "permissions": [ 1428 | "Website And Security Policy Management", 1429 | "System Management", 1430 | "User Management", 1431 | "Audit Log Management" 1432 | ], 1433 | "authentication_method": "password", 1434 | "session_timeout_time": 172800 1435 | }, 1436 | "extra": {} 1437 | }, 1438 | "msg": null 1439 | } 1440 | ``` 1441 | 1442 | # /api/DownloadUserCertAPI 1443 | 1444 | ## GET 1445 | 1446 | ### [数据格式定义] 1447 | 数据格式 1448 | 1449 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1450 | |:--|:-:|:-:|:-:|:-:| 1451 | |token | 字符串 | 必填 | False | - | 1452 | 1453 | ?> 不需要登录 1454 | 1455 | ### - 下载证书认证的用户的证书 1456 | 1457 | 1458 | 1459 | 请求 1460 | 1461 | ```js 1462 | { 1463 | "token": "e15966530aaafdcee1f91a6065c0c3c0:1gWcW2:Ap4j2DLdtcVbYrjnFQKAVxQ1rC4" 1464 | } 1465 | ``` 1466 | 1467 | 响应 1468 | 1469 | ```js 1470 | { 1471 | "err": null, 1472 | "data": null, 1473 | "msg": null 1474 | } 1475 | ``` 1476 | 1477 | # /api/UsernameExistsCheckAPI 1478 | 1479 | ## GET 1480 | 1481 | ### [数据格式定义] 1482 | 数据格式 1483 | 1484 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1485 | |:--|:-:|:-:|:-:|:-:| 1486 | |username | 字符串 | 必填 | False | 最大长度: 16; | 1487 | 1488 | ### 权限 1489 | - User Management 1490 | 1491 | ### - 查询一个用户名是否被占用 1492 | 1493 | 1494 | 1495 | 请求 1496 | 1497 | ```js 1498 | { 1499 | "username": "foobar" 1500 | } 1501 | ``` 1502 | 1503 | 响应 1504 | 1505 | ```js 1506 | { 1507 | "err": null, 1508 | "data": { 1509 | "result": false 1510 | }, 1511 | "msg": null 1512 | } 1513 | ``` 1514 | 1515 | # /api/UnlockUserAPI 1516 | 1517 | ## PUT 1518 | 1519 | ### [数据格式定义] 1520 | 数据格式 1521 | 1522 | > 只需要传递 ID 1523 | 1524 | |字段名|数据类型|是否必填/默认值|NULL|其他| 1525 | |:--|:-:|:-:|:-:|:-:| 1526 | |id | 整型数字 | 必填 | False | - | 1527 | 1528 | ### 权限 1529 | - User Management 1530 | 1531 | ### - 解锁用户 1532 | 1533 | 1534 | 1535 | 请求 1536 | 1537 | ```js 1538 | { 1539 | "id": 26 1540 | } 1541 | ``` 1542 | 1543 | 响应 1544 | 1545 | ```js 1546 | { 1547 | "err": null, 1548 | "data": null, 1549 | "msg": null 1550 | } 1551 | ``` 1552 | 1553 | --------------------------------------------------------------------------------