漫谈SDL之三方库漏洞检测与CICD

  • A+
所属分类:安全开发

漫谈SDL之三方库漏洞检测与CICD


背景


最近有小伙伴在做落地、推动三方包漏洞检测时遇到了一些问题:与devops工具侧同学以及业务线研发同学沟通时,存在双方不理解对方表述,鸡同鸭讲的情况。

解答了几次相关问题后,发现这些问题有一些共性:都会涉及到研发流程、CICD、工程化的一些点,比如CICD具体是哪些,commitbuild这些阶段是属于哪个阶段,具体是什么含义,哪个是合适的安全检测触发点,有实现安全左移么?工具侧同学说的gitlab-cirunner.gitlab-ci.ymlpipeline又是什么,安全检测与这些又是什么关系等等)。这些知识对安全同学一定程度上来说是陌生的,但又是研发安全在企业里落地推动时的基础。在网上查到的资料一般是对于这些名词概念的单独描述,并没有与安全结合。所以稍作整理,或许可以提供参考价值。

问题


问题一:

CI/CD该如何理解,包含哪些过程及产出,会涉及哪些工具?

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

持续集成指的是,频繁地(一天多次)将代码集成到主干。 

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

Martin Fowler说过,"持续集成并不能消除Bug,而是让它们非常容易发现和改正。"

个人觉得CI可以直观的理解为包含:代码提交合并、build构建、打包出包(出包就是可以进行部署的文件,如jarexedocker镜像等等)的一个过程,那放在SDL中来说就是开发环节。

打包完成后的产物就可以进行部署,通俗来说部署可以理解成将应用程序安装到服务器上运行,提供服务。CD有两种解释,“持续交付”与“持续部署”,这里引用《持续集成是什么?》(作者:阮一峰)中的解释来看:

持续交付(Continuous delivery)指的是,频繁地将软件的新版本,交付给质量团队或者用户,以供评审。如果评审通过,代码就进入生产阶段。

持续交付可以看作持续集成的下一步。它强调的是,不管怎么更新,软件是随时随地可以交付的。

持续部署(continuous deployment)是持续交付的下一步,指的是代码通过评审以后,自动部署到生产环境。

持续部署的目标是,代码在任何时刻都是可部署的,可以进入生产阶段。持续部署的前提是能自动化完成测试、构建、部署等步骤。

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

涉及不同的代码分支、随时的提交、合并,还有发布、部署因为人为差异等因素产生的bug等,这些集成或部署工作及问题以前是由人工完成的。但是现在鼓励持续集成,那岂不是要累死人,还会影响开发效率,所以希望有自动化软件集成部署解决方案,也就是持续集成系统CICD。

要实现持续集成、持续交付、持续部署,基础是要有相应的工具链来进行支撑(当然同时还需要研发流程的统一、相关人员对工具流程的熟悉掌握)。涉及到的流程及工具可以通过图2有更直观的理解。
漫谈SDL之三方库漏洞检测与CICD
图2

图1中的代码管理具体实现就是图2中的git或svn(其实SVN的年代已经过去了,现在更多是git,有github、gitlab,还有一些轻量级的,gitlab应该是企业里用的比较多的,同时gitlab目前也提供了完善的CICD能力,有内置工具,如gitlab-ci,可以替换上图中的Jenkins这样的三方工具。另外gitlab UI可以将所有步骤可视化(包括质量检测、安全检测等),这样比起来,使用gitlab更丝滑。其完整的CICD工作流程如下图3所示:
漫谈SDL之三方库漏洞检测与CICD
图3



问题二:


三方包检测如何实现,在CICD中哪个阶段比较好?

对于安全同学来说,三方包的实现机制应该还是比较清楚的,即应用所依赖的三方库与漏洞库(cve、cnvd等)做对比,看是否有使用有漏洞的三方库版本。这样拆分出三个需求:获得应用所依赖的三方库、获得漏洞库、做对比得出结果并通知修复。

这里主要说下第一个需求(获得应用所依赖的三方库)如何实现?应用程序如果使用开源三方库,一般会在配置文件中列出依赖包及其版本。所以最简单的方式就是去读取分析这个配置文件来获取依赖项信息(当然还有二次依赖的问题,这里先不做讨论。题外话,sdl/devsecops建议先做覆盖率跑起来,再做优化)。
不同的语言有不同的配置文件:
语言
配置文件
golang
go.sum
java
pom.xml
java
bulid.gradle
python
requirements.txt
nodejs
package.json
php
composer.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是基于自动化脚本的,那如何将这个脚本串联触发并最终形成“持续”的效果?那就是如gitlab-ci中的runner、.gitlab-ci.yml、pipeline这些概念。
这里主要说下gitlab-ci相关的:(网上资料较多,这里概括说一下)
• pipeline:是一个概念—任务流,没有具体的实体(见图4)。在gitlab侧边栏中是可以看到这个的,其实就是构建中的阶段(stages)集合,比如自动构建、自动进行单元测试、代码审计等等,会按照顺序执行,所有阶段(stages)执行成功后,才算构建任务(pipeline)执行成功;如果某一个stage失败,后续不再执行,构建任务失败;而一个阶段(stage)可以包含多个job,这些job可以并行执行,某个失败即stage失败;这些stages、job都是定义在.gitlab-ci.yml中的。
漫谈SDL之三方库漏洞检测与CICD
图4

  * 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"
# 定义 jobjob2: stage: test #如果此处没有定义stage,其默认也是test only: - master #只有master分支提交代码才会执行这个任务 script: - echo "I am job2" - echo "I am in test stage" allow_failure: true #允许失败,即不影响下步构建
# 定义 jobjob3: 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://[email protected]/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:    - shared-shell  script:     # 执行操作:发送依赖文件pom及对应的commit id 、owner,传给三方库漏洞检测服务进行检测及通知owner    - file=` $DEPEND_FILE `    - echo {”owner":"$GITLAB_USER_EMAIL","app_name":"$PROJECT_NAME","branch":"$CI_COMMIT_BRANCH","commit_id":"$CI_COMMIT_SHA","lang":"java","app_from":"git","data":"$file","build_task_id":"$CI_JOB_ID"}  only:    - branches    - tags

这样就实现了上面所说的将安全检测服务与commit操作的触发、联动,达到持续集成中进行三方库漏洞检测并通知owner的效果。当然这里的检测服务可以自己实现,也可以直接使用开源或商业的,如Dependency-Check等。很多场景下,写配置文件都用YML,也有YML工程师的戏称。

以上基本就是三方库漏洞检测落地推广时涉及到CICD相关的一些基础知识、问题和解决思路,其实不光是三方包漏洞检测可以和CI集成,在CD中对系统、中间件、镜像基线的检测都可以集成,这些可以根据企业自己的场景来集成契合。

“如何将安全契合进研发”,这个问题以及解决逻辑会一直伴随SDL/Devsecops的推动落地,理解这些有助于我们落地,推广,希望对SDL萌新们有所帮助吧~


本文始发于微信公众号(哔哩哔哩安全应急响应中心):漫谈SDL之三方库漏洞检测与CICD

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: