[toc]
# BDG
Building door god —— 构建门神,即构建打包过程的检测方案。
直白点讲就是提供了一个扫描服务,实时扫描代码并保存结果,然后将结果在后台界面可视化展示。同时可以将它无缝接入到现有的代码编译部署环节。 为了拓展性我们的扫描系统基于插件机制(或者说插件组件)开发,并支持自定义配置。最后还支持本地调试。
# 背景
一般而言,每个公司的开发团队都有着较为完善的脚手架支持和git commit规范,并搭配 githook(precommit) 以确保每次提交的代码都是经由 eslint校验通过的,但这里面也有几个显而易见的漏洞:
- 本地.eslintrc文件中的规则可以随意修改
- 可以通过 git commit -n 指令轻易绕过githook设置的钩子
- 项目中可能出现失效的404链接
- 项目三方依赖包中存在已公开或未公开漏洞
- 代码中可能出现不符合项目规范的业务逻辑代码:比如用location.href=xx而不是用项目自身的路由api
# 核心实现
技术实现上的细节,主要是扫描代码功能这部分的实现,这是整个系统的核心,无论是服务器端还是本地扫描,都会调用这部分能力,所以我们将它单独抽离成一个包,暂时叫做 melodyCheckSevice 。 如下图所示 melodyCheckSevice 的工作流程简要如下:
接受配置参数,或者说入参: 即项目源码地址、项目自身配置 + 自定义配置;
准备工作: 获取插件的npm包,处理插件配置+入参,合成符合工作流的scheme-json配置形态;
在服务端:
- 我们需要获取项目对应的分支信息,从GitLab上拉取当前分支的最新代码,并解压缩到特定的临时目录(方便扫描之后删除,节省硬盘使用),
- 从数据库读取当前项目的插件配置,从 npm 源检查每一个插件的最新版本,与本地插件版本对比
- 下载新插件、更新旧插件
在本地调试时:
- 与服务端不同,我们本来就身处项目中,所以不需要拉取源码
- 从服务端请求当前项目的插件配置
- 从 npm 源检查每一个插件的最新版本,与本地插件版本对比
- 下载新插件、更新旧插件
最后一步安装插件时,有一个细节值得注意——这里的插件npm包管理应该是独立管理的,不能和当前运行环境的node_modules文件夹混合,我们可以将插件安装到指定目录,通过 --prefix 修饰符可以实现。
npm install pluginName --prefix targetDir
引入插件时,再次通过 require.resolve 覆盖 require 方法默认的解析顺序
const path = require.resolve(pluginName, { paths: [targetDir] })
const plugin = require(path)
- 代码校验: 我们从拿到的插件配置中,遍历运行当前项目所用到的插件,得到一个包含每个插件输出日志的数组,存到日志中备用。
- 代码评分:
从三个维度扫描代码,得到一个包含每个维度打分详情的数组,并存到日志中备用。
三个维度:
- 代码规范
- 代码可维护度
- 代码重复度
- 输出结果: 扫描结果分为两大类,日志和评分,这分别对应代码校验和代码打分这两步; 最后把扫描结果返回给调用方——在服务端则存储到服务器,在本地则生成本地日志文件,接入其他平台就同步返回给平台(如libra)。
需要注意的是,整个扫描过程根据项目大小不同,短则几十秒,长则几分钟。在服务端运行时,这么长的执行时间即使是异步操作,也会造成服务器的 I/O 不可接受的延迟,所以必须使用多进程能力,在子进程中执行扫描,只在扫描结束时把返回值发送给主进程,存储到数据库中。
# 方案设计
# 平台服务化+插件(组件)机制
我们的门神系统设计,由一个平台服务+插件机制组成,平台服务给各类插件提供运行环境和数据、配置可视化功能。实现这一步,需要完成以下工作:
- 搭建插件管理平台
- 支持注册自定义插件
- 为用户开发插件提供文档和技术支持
- 支持对不同的项目配置不同的插件组合
系统通过遍历执行插件完成扫描,每个插件各司其职,可以自定义添加到项目配置当中。
# 接入相关发布部署平台
只需要在系统触发上述四个钩子:
- 在创建分支时,通知门神服务存储分支基础信息,为后续的代码校验做准备
- 在构建代码时,通知门神服务当前分支代码已更新,可以开始校验
- 在部署测试/沙箱时,通知门神服务,可以开始页面测试
- 最终正式上线时,从门神服务读取校验结果,根据结果判断是否拦截上线操作
# 建设可视化后台
详细的校验结果是从门神服务的服务端读取的,门神服务的服务端记录着每个项目、每个分支、每次构建的静态扫描记录,每次记录都会根据项目的配置,返回一个“是否通过”的最终结果。这个结果既供接入系统读取,也供后台界面可视化访问。
后台主要分为三大板块:
- 以项目整体维度粗略预览(列表)
- 以单次构建维度查看详细报错信息(详情)
- 项目配置(配置详情查看)
以单次构建维度查看详细报错信息:
系统采用插件机制,通过遍历插件完成扫描,不同插件实现不同的拦截需求。详见后面的“错误等级划分”;
这个界面还可以查看项目的代码质量评分。参考icoworks的评分实现,我们从代码规范、代码可维护度和代码重复度三个方面对代码进行打分,并给出修改建议,但不做强制要求,也不会进行拦截。
# 支持本地调试
可以集成到ty-cli工具里,在项目根目录下通过ty check指令一键扫描,扫描完成后会起一个本地服务,获得和线上页面一样的浏览体验。不一样的地方在于:
- 更完整的日志(线上为了节约存储空间,相对会做些删减);
- 保留eslint自动修复能力
- 可以通过点击文件链接直接跳转到文件进行修改
# 错误等级划分
问题:如何制定拦截规则。
目的:既不希望门神系统过于严格,成为业务上线的绊脚石,屡屡阻拦业务正常上线,影响开发效率,这个违背了提质增效的本意;也不希望过于放松,起不到“门神”的作用,徒增摆设,最后无人问津。
基于这个考虑,划分了错误等级。系统的扫描结果分为两大类:一个是日志,一个是评分。
划分错误等级指的是对日志中的错误进行分级,日志是系统通过遍历插件扫描项目的返回值,用于判断是否需要拦截项目上线。
目标在于发现项目中不被察觉的低级错误和潜在的安全问题。插件根据自己的规则,返回统一的返回值,分为三个级别:error、warning和info。
error级别的日志默认会触发拦截,而warning和 info 级别则只是告知,不做强制要求。
# 插件
从内容来讲主要涵盖三个方面:
- lint —— 代码校验
- package lib check —— 包依赖安全检测;
- building bulk check —— 构建大小检测;
这些构建检测功能包从开发到落地的流程,同时要伴随着对应的规范落地
落地方案
- 定规范:
- 开发对应的功能组件
- 开发构建时集成到发布平台的流程卡点组件;
- 本地开发时的包依赖(或者配置依赖) 结合git hoook(如hushy包);
- 推规范
- 直接集成到项目模板;
- 对于老项目,一般按业务线辅助落地,渐进式推进;
- 集成到发布平台
# lint
lint —— 代码校验,主要从两方面:
- 代码风格检测;
- 定制化lint插件检测;(如检测代码中是否含有location不规范跳转)
# 技术选型
- eslint
- tslint
- 自定义lint-plugin开发
# eslint主体
主体这块就是基于eslint规则配置文件做代码校验。
# eslint插件扩展
由于业务代码中存在其他的问题,要单独做eslint-plugin,对应配置中的plugins字段。
https协议校验扩展
- 背景:前端项目中的http链接可以分为三类,即 http链接,https链接,相对链接。而http链接相对不安全,期望通过此配置扩展能赋予eslint以检测http链接的能力。当然,如果项目各种环境都是直接挂https,那么可以暂不考虑相对链接。
- 目标:使用此扩展,则在eslint的输出结果里,加入描述含有http链接总数和详情的对象字段。
- 方案:eslint-plugin 扩展包封装。
location不规范跳转检测扩展
- 背景:前端项目中,有些同学会不按照规范去进行页面路由和跳转,比如直接location.href=xxx;
- 目标:使用此扩展,则在eslint的输出结果里,加入描述含有location不规范跳转总数和详情的对象字段;
- 方案:eslint-plugin 扩展包封装。
# package lib check
package lib check —— 包依赖安全检测,主要从两方面:
- 对项目依赖树的包版本分析,基于开源的漏洞库扫描已知的包版本漏洞
- 对项目依赖树的许可证书进行分析,基于package.licence + 项目自定义白名单 扫描出无许可证或者不正常许可证的依赖。
# 技术选型
开源项目
- 如果你的项目是开源的,那么 snyk 无疑是最佳选择,它可以完美的解决上述1的问题。snyk只对开源项目完全免费,对闭源项目每天只提供200次扫描机会。
npm audit
- NPM集成功能,源于08年收购的nsp;
- 原理: 基于整合的具备已知漏洞的包版本资料库,在npm上整合漏洞扫描的能力。
- 前置条件:lock文件
- npm audit
- 支持JSON/文本报表
yarn audit
- YARN集成功能
- 原理: 基于具备已知漏洞的包版本资料库, 对yarn list进行扫描。
- 前置: yarn.lock
- 支持JSON/文本报表
- 优点: 报表和JSON格式相对更完善。
retirejs
- 一个开源的依赖监测工具,提供命令行和Grunt, Firfox, Chrome等插件。从NIST NVD,漏洞追踪系统,博客和邮件列表等收集漏洞数据。
- Retire.js的目标是帮助您检测具有已知漏洞的版本的使用。
- 原理:具有已知漏洞的包版本资源库。
- Retire.js从四个角度去扫描:
- Library
- From version
- Up to version //最新版本
- Links
- 缺点:漏洞库相对不完善。
# 解决项目兼容性问题
有的项目用的yarn,有的npm,有的cnpm等。。。 我们需要抹平用不同工具得到的result输出差异!
以 yarn audit --json 的结果数据结构为基准
# 设计
以 yarn audit输出为基准格式,抹平三方面的差异
{
//...other-property
"currentResult": {
"type": "auditSummary",
"data": {
"vulnerabilities": {
"info": 0,
"low": 0,
"moderate": 0,
"high": 0,
"critical": 0
},
"dependencies": 29,
"devDependencies": 0,
"optionalDependencies": 0,
"totalDependencies": 29
}
// ...other-property
}
# 实现参考
音巢开源组织(https://melody-core.github.io/)在npm上输出了一些开源的公众包,其中 @melody-core/holmes 就是 package-lib-check 的一种具体实现。
- 您可以讲福尔摩斯当作一个依赖去使用
# 安装
yarn add @melody-core/holmes
// 使用
const PackageSafeWorker = require('@melody-core/holmes');
const packageSafeWorker = new PackageSafeWorker({
proPath //参数,要检测项目的绝对路径
});
const result = await PackageSafeWorker.run();
console.log('项目依赖包安全检测结果:', result)
- 同时,福尔摩斯本身就是一个命令行工具
yarn global add @melody-core/holmes
# 您可以在任意前端项目的根目录下运行
holmes check
# 我们更期望您可以通过 melody来安装它。
yarn global add @melody-core/melody-cli
melody i @melody-core/holmes
# 您可以在任意前端项目的根目录下运行下方命令来检测您的项目是否含有已知漏洞。
melody holmes check
# building bulk check —— 构建产物大小检测
这一块内容,主要对小程序类项目有着至关重要的作用。当然,也可以制定web/h5项目的一些指标。防止打包出的产物过大,带来用户体验上的损耗。
# 写在最后
音巢开源组织建立快一个月啦~感兴趣的同学请直接在本公众号内留言自己的钉钉联系方式~
音巢官网:https://melody-core.github.io/