三方库漏洞检测与CI/CD

摘要:
#########背景最近,一些小伙伴在实施和推广第三方包漏洞检测方面遇到了一些问题:但这也是企业实施和推广研发安全的基础;“持续集成并不能消除错误。代码提交和整合、构建和打包(包交付是一个可以部署的文件。一般来说,部署可以理解为在服务器上安装应用程序以运行。持续部署的前提是自动完成测试、构建、部署和其他步骤。集成和部署过程中会出现许多问题。
#### ### ## 背景

最近有小伙伴在做落地、推动三方包漏洞检测时遇到了一些问题:与devops工具侧同学以及业务线研发同学沟通时,存在双方不理解对方表述,鸡同鸭讲的情况;
解答了几次相关问题后,发现这些问题有一些共性:都会涉及到研发流程、CICD、工程化的一些点;
(比如CICD具体是哪些,commit、build这些阶段是属于哪个阶段,具体是什么含义,哪个是合适的安全检测触发点,有实现安全左移么?工具侧同学说的gitlab-ci、runner、.gitlab-ci.yml、pipeline又是什么,安全检测与这些又是什么关系;等等),这些知识对安全同学一定成度上来说是陌生的,但又是研发安全在企业里落地推动时的基础;
在网上查到的资料基本上是单独描述这些名词概念,并没有与安全结合,所以稍作整理,也许有点分享价值;

问题:
问题一:CI/CD该如何理解,包含哪些过程及产出,会涉及哪些工具;

CI(Continuous Integration)持续集成,这里引用《持续集成是什么?》(作者:阮一峰)中的解释来看一下:

持续集成指的是,频繁地(一天多次)将代码集成到主干。
(1)快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。
(2)防止分支大幅偏离主干。如果不是经常集成,主干又在不断更新,会导致以后集成的难度变大,甚至难以集成。
持续集成的目的,就是让产品可以快速迭代,同时还能保持高质量。它的核心措施是,代码集成到主干之前,必须通过自动化测试。只要有一个测试用例失败,就不能集成。
Martin Fowler说过,"持续集成并不能消除Bug,而是让它们非常容易发现和改正。"

个人觉得CI可以直观的理解为包含:代码提交合并、build构建、打包出包(出包就是可以进行部署的文件,如jar、exe、docker镜像等等)的一个过程;
打包完成后的产物就可以进行部署,通俗来说 部署可以理解成将应用程序安装到服务器上运行,提供服务;

CD有两种解释,“持续交付”与“持续部署”,这里引用《持续集成是什么?》(作者:阮一峰)中的解释来看一下:

持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。
持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。
持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。
持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。
持续部署的前提是能自动化完成测试、构建、部署等步骤。

这里有个背景是:集成的工作一般会比较细碎繁琐,为了不影响开发效率,在以前,集成这个环节只会等到项目后期才进行,而不是频繁发生。但是如果等到后期才发现并解决问题,代价就很大,有可能导致项目延期或者失败(比如像安全漏洞这种高优问题~ :)。因此,为了尽早发现软件错误,应当鼓励团队成员应该经常集成他们的工作,通常每个成员每天应该至少集成一次。这就是所说的持续集成。所以说,持续集成是一种软件开发实践。这一点上与我们所说的sdl/devsecops是相似的,都是讲求项目质量把控;

那CICD,说白了还是在多人合作的工程化项目中,在集成、部署时产生了众多问题,比如合并提交代码,涉及不同的代码分支、随时的提交、合并、发布、部署产生的bug等,这些集成或部署工作以前是由人工完成的。但是现在鼓励持续集成,那岂不是要累死人,还影响开发效率。所以希望实现自动化的软件集成解决方案,也就是持续集成系统CICD;
image

要实现持续集成、持续交付、持续部署,前提是要有相应的工具链来进行支撑(当然同时还需要研发流程的统一、涉及人员对工具流程的熟悉)
涉及到的流程及工具可以通过图示有更直观的理解:
三方库漏洞检测与CI/CD第2张

图一中的,代码管理平台就是代码仓库,具体有图二中的git或svn(其实SVN的年代已经过去了,现在更多是git,同时gitlab目前也提供了完善的CICD能力,有内置工具,如gitlab-ci,就可以替换上图中的Jenkins;另外gitlab UI可以将所有步骤可视化(包括质量检测、安全检测等),这样比起来,使用gitlab更丝滑~其完整的CICD工作流程如下图所示
三方库漏洞检测与CI/CD第3张

问题二 :三方包检测如何实现,在CICD中哪个阶段比较好;

首先 对于安全同学来说,三方包的实现机制应该还是比较清楚的:应用所依赖的三方库与漏洞库(cve、cnvd等)做对比,看是否有使用有漏洞的三方库版本;
这样拆分出三个需求:获得应用所依赖的三方库、获得漏洞库、做对比得出结果并通知修复;
这里主要说下第一个需求如何实现--获得应用所依赖的三方库:应用程序如果使用了开源三方库,一般会在配置文件中列出依赖包及其版本,所以最简单的方式就是去读取分析这个配置文件来获取依赖项信息;(当然还有二次依赖的问题,无法解决,这里不做讨论;题外话:sdl/devsecops建议先做覆盖率跑起来,再做优化)
不同的语言有不同的配置文件:
go.sum(golang),pom.xml(java-maven),bulid.gradle (java-gredle)、requirements.txt(python),package.json(nodejs),composer.lock(php);

语言配置文件
golanggo.sum
javapom.xml
javabulid.gradle
pythonrequirements.txt
nodejspackage.json
phpcomposer.lock

那在CICD的哪个阶段来实现呢,我们做sdl核心的思想是“安全左移”,要让研发同学尽早感知到安全问题,尽早解决,从这个点来说当然是越早越好,比如在研发同学本地开发时,可以通过在本地环境中集成插件来实现,比如在maven项目中,可以在pom文件中增加配置即可,具体可以看https://yq.aliyun.com/articles/698621;
但这个方法不太好做做企业层面的推广,你无法要求每个人都在自己的本地环境/本地分支来增加配置,这种方式在个人开发或小团队比较适用(这种场景一般也不会有CICD啦~)
如果在build构建时进行检测,对于项目成员来说会觉得有点晚了,因为此时已经要打包了,再回头去改就比较麻烦;
对我们来说最合适的是在commit阶段,代码提交到代码管理平台(对我们来说是gitlab仓库)时就触发,研发同学就能知道并有时间作出及时的更新;这样是符合安全左移,也符合CI的思想:“快速发现错误。每完成一点更新,就集成到主干,可以快速发现错误,定位错误也比较容易。”
我们也是经过了几种方式的变动尝试得出了适合我们的方式,大家可以根据自己的实际情况来制定;

问题三:“工具侧同学说的gitlab-ci、runner、.gitlab-ci.yml、pipeline又是什么,安全检测与这些又是什么关系”

在明确了检测机制、确定了检测节点,需要在CICD/devops工具链中集成实现,这个时候需要跟平台工具侧同学沟通,就需要了解CICD的基本知识和实现机制:CICD是基于自动化脚本的,那如何将这个脚本串联触发,最终形成“持续”的效果?那就是如Jenkins的job概念、如gitlab-ci中的runner、.gitlab-ci.yml、pipeline这些概念;
这里主要说下gitlab-ci相关的:(网上资料较多,这里概括说一下)

  • pipeline:是一个概念--任务流,没有具体的实体;在gitlab侧边栏中是可以看到这个的,其实就是构建中的阶段(stages)集合,比如自动构建、自动进行单元测试、代码审计等等,会按照顺序执行,所有阶段(stages)执行成功后,才算构建任务(pipeline)执行成功,如果某一个stage失败,后续不再执行,构建任务失败;而一个阶段(stage)可以包含多个job,这些job可以并行执行,某个失败即stage失败;这些stages、job都是定义在.gitlab-ci.yml中的;
    image* runner:一般每个gitlab工程都会定义一个属于这个工程的软件集成脚本,用来自动化的完成软件集成工作,而gitlab-runner就是用来执行这些脚本的,可理解为jobs的执行器;使用Runner需要进行安装和注册,我们这里知道有这样的一个机制就好,具体的方法可以看这篇文章https://www.cnblogs.com/cnundefined/p/7095368.html 讲的非常详细了;
  • .gitlab-ci.yml:用来指定构建、测试和部署流程、以及CI触发条件的脚本,Gitlab检测到.gitlab-ci.yml文件,若当前提交(commit)符合文件中指定的触发条件,则会使用配置的gitlab-runner服务运行该脚本进行测试等工作;存在于项目根目录下;
    示例如下
stages:
  - build
  - test
  - deploy

# 定义 job(任务)
job1:
  stage: test
  tags:
    - XX #只有标签为XX的runner才会执行这个任务
  only:        
    - dev    #只有dev分支提交代码才会执行这个任务。也可以是分支名称或触发器名称
    - /^future-.*$/ #正则表达式,只有future-开头的分支才会执行
  script:
    - echo "I am job1"
    - echo "I am in test stage"

# 定义 job
job2:
  stage: test    #如果此处没有定义stage,其默认也是test
  only:
    - master    #只有master分支提交代码才会执行这个任务
  script:
    - echo "I am job2"
    - echo "I am in test stage"
  allow_failure: true #允许失败,即不影响下步构建    

# 定义 job
job3:
  stage: build
  except:    
    - dev #除了dev分支,其它分支提交代码都会执行这个任务
  script:
    - echo "I am job3"
    - echo "I am in build stage"    
  when: always #不管前面几步成功与否,永远会执行这一步。它有几个值:on_success (默认值)on_failurealwaysmanual(手动执行)
    
# 定义 job
.job4:    #对于临时不想执行的job,可以选择在前面加个".",这样就会跳过此步任务,否则你除了要注释掉这个jobj外,还需要注释上面为deploy的stage
  stage: deploy
  script:
    - echo "I am job4"    

# 模板,相当于公用函数,有重复任务时很有用
.job_template: &job_definition  # 创建一个锚,'job_definition'
  image: ruby:2.1
  services:
    - postgres
    - redis

test1:
  <<: *job_definition           # 利用锚'job_definition'来合并
  script:
    - test1 project

test2:
  <<: *job_definition           # 利用锚'job_definition'来合并
  script:
    - test2 project    

#下面几个都相当于全局变量,都可以添加到具体job中,这时会被子job的覆盖    

before_script:
  - echo "每个job之前都会执行"    
  
after_script:
  - echo "每个job之后都会执行"    
  
variables:    #变量
  DATABASE_URL: "postgres://postgres@postgres/my_database"  #在job中可以用${DATABASE_URL}来使用这个变量。常用的预定义变量有CI_COMMIT_REF_NAME(项目所在的分支或标签名称),CI_JOB_NAME(任务名称),CI_JOB_STAGE(任务阶段)
  GIT_STRATEGY: "none" #GIT策略,定义拉取代码的方式,有3种:clone/fetch/none,默认为clone,速度最慢,每步job都会重新clone一次代码。我们一般将它设置为none,在具体任务里设置为fetch就可以满足需求,毕竟不是每步都需要新代码,那也不符合我们测试的流程

cache:    #缓存
  #因为缓存为不同管道和任务间共享,可能会覆盖,所以有时需要设置key
  key: ${CI_COMMIT_REF_NAME}  # 启用每分支缓存。
  #key: "$CI_JOB_NAME/$CI_COMMIT_REF_NAME" # 启用每个任务和每个分支缓存。需要注意的是,如果是在windows中运行这个脚本,需要把$换成%
  untracked: true    #缓存所有Git未跟踪的文件
  paths:    #以下2个文件夹会被缓存起来,下次构建会解压出来
    - node_modules/
    - dist/  

以我们的三方库漏洞检测为例,就需要添加如下示例代码,以java-maven项目为例

stages:
  - build
 
variables:
  PROJECT_NAME: xxx.test
  # 需要发送的安全依赖文件 pom.xml
  
  DEPEND_FILE: pom.xml
 
build:
  stage: build
  tags:
    - rider-shared-shell
  script:
    # 发送安全依赖文件 执行xxx(检测、通知)
    - file=` $DEPEND_FILE `
    - echo xxx

  only:
    - branches
    - tags

很多场景下,写配置文件都用YML,也有YML工程师的戏称;

以上基本就是三方库漏洞检测落地推广时涉及到CICD相关的一些基础知识、问题和解决思路,
“如何将安全契合进研发”,这个问题以及解决逻辑会一直伴随SDL/Devsecops的推动落地,理解这些有助于我们落地,推广;
希望对SDL萌新们有所帮助吧~

免责声明:文章转载自《三方库漏洞检测与CI/CD》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用Cucumber+Rspec玩转BDD(7)——测试重构洛谷训练新手村之“BOSS战入门综合练习1”题解下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

SonarQube系列一、Linux安装与部署

【前言】 随着项目团队规模日益壮大,项目代码量也越来越多。且不说团队成员编码水平层次不齐,即便是老手,也难免因为代码量的增加和任务的繁重而忽略代码的质量,最终的问题便是bug的增多和代码债务的堆积。因此,代码review便日益提上了日程。当然人工review的效率还是相当低下的,于是我们采用了自动化代码review的工具,便是今天的主角:SonarQube...

UDP广播-缓冲区过小

       上次介绍到了关于客户端实时刷新摄像头所识别的图片的一些方法,采用了了UDP广播的技术做处理。理论上是没有问题的,将客户端运行在不同电脑上也能很好的看到效果,运行日志也没看出啥问题,结果今天翻看日志的时候发现提示缓冲区异常。加一个长度断点之后,发现了当图片转化的【byte】数组长度大于110K 的时候,就会出现缓冲区异常。日志部分截图如下 根...

java.net.URISyntaxException的解决办法

近日在用HttpClient访问抓取汇率时,为了省力,直接采用 String url = "http://api.liqwei.com/currency/?exchange=usd|cny&count=1"; HttpClient client = new DefaultHttpClient(); HttpGet httpget = new Htt...

client intended to send too large body

2012/11/14 19:15:20 [error] 16038#0: *1281 client intended to send too large body: 1797176 bytes, client: 10.96.54.125, server: localhost, request: "PUT /svn/repos/!svn/txr/44-1i/...

mongo分页查询,同时返回分页记录和记录数

今天做了一个mongo的分页查询,记录一下mongo如何同时返回分页记录和记录数的方法,利用的是mongo的聚合查询里面的$facet阶段,具体使用参考官方文档即可https://docs.mongodb.com/manual/reference/operator/aggregation/facet/ 按照官方文档的说法, Input documents...

【JAVA】将PDF转化成图片

很多的开源库可以完成这个任务,但是效果都不理想,主要会产生以下问题: 1)分辨率很低,就是最后转化成的图片是缩略图,放大之后看不清楚; 2)不能支持中文,所有中文一律乱码; 3)图片各种混淆:大体是对的,但是个别元素出现混乱,尺寸位置不对等等; 上面说到的三种问题在各种类库中或多或少都存在。 A)实验下来效果最好的是QOPPA的开源库,在免费部分的测试情况...