• 三方库漏洞检测与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

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

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

    问题二 :三方包检测如何实现,在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);

    语言 配置文件
    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是基于自动化脚本的,那如何将这个脚本串联触发,最终形成“持续”的效果?那就是如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萌新们有所帮助吧~

    有不对的地方欢迎大家指正,谢谢
  • 相关阅读:
    SVN使用svn+ssh协议连接服务器时重复提示输入密码 解决办法
    SQL Server 2008 排序函数 ROW_NUMBER和RANK 用法总结
    数据表基础知识(1)
    数据库基本概念
    String函数
    委托
    递归算法
    关于C#引用类型赋值
    BackgroundWorker的应用
    DevExpress_Report 主从报表绑定数据,分页打印
  • 原文地址:https://www.cnblogs.com/zjdyl/p/14549634.html
Copyright © 2020-2023  润新知