DevSecOps实战
上QQ阅读APP看书,第一时间看更新

3.4 持续集成

持续集成(Continuous Integration)是一种软件开发实践,其目的就是让产品可以快速迭代,同时还能保持高质量。它的操作措施是开发人员频繁地将他们对源码的变更提交到一个单一的代码库,在集成到主干之前,通过自动化构建和测试等操作验证这些改变是否可以正常工作,只要有一个测试用例失败,就不能集成,从而更早地得到反馈和发现错误。持续集成包括以下几大特点:

  • 访问单一源码库,将所有源代码存储在同一地点,使得所有有权限的开发人员可以从这里获取不同版本和最新的源代码。
  • 提倡开发人员频繁提交修改过的代码,可以防止集成变得复杂甚至推迟。
  • 支持自动化编译构建,合并代码到主干前进行构建,如果构建失败则暂停提交新代码,直到构建问题被修复,保证构建一直处于成功状态。
  • 测试尽量自动化,包括单元测试、功能测试等。
  • 有针对性地向相关人员发送反馈,比如构建和测试结果、问题以及缺陷。及时的反馈可以让开发人员快速修复失败的构建和测试,并且可以有效抑制缺陷的蔓延和积累,减少成本。

3.4.1 编译构建和开发环境安全

编译是指将源代码转化成目标程序的过程。这个过程一般来说分为五个阶段:词法分析、语法分析、中间代码生成、代码优化和目标代码生成。为了执行编译,往往需要搭建相关编译环境,也称集成开发环境(Integration Development Environment,IDE),包括编译器,还会有其他一些工具被打包在一起,统一发布和安装。除了IDE,有时还需要安装其他一些程序,比如Java编译环境除了需要安装IDE(比如Eclipse),还需要安装Java Develop Kit (JDK)以及相关环境变量的配置。常见的构建工具也是对应相关的开发语言,比如Java (Maven、Gradle、Ants)、C# (MSBuild、Nants)、C++(Cmake)等。

软件开发环境由软件工具和环境集成机制构成,前者用来支持软件开发的相关过程、活动和任务,后者为工具集成和软件的开发、维护及管理提供统一的支持。一个完整的开发环境包括操作系统(Linux、Windows等)、IDE、构建依赖包,以及相关的环境变量。其最终目的是可以使得源代码在本身正确无误的情况下成功转化/打包成应用程序。然而,开发环境中的操作系统、构建依赖包等第三方提供的软件,都有可能主动或者被动地被感染,成为安全隐患的源头。

2015年著名的XcodeGhost安全事件,就是病毒制造者通过感染苹果应用的开发工具Xcode,让AppStore中的(通过被感染的Xcode开发过的)正版应用带上了会向指定网站上传用户信息的恶意程序。据估算,受到影响的用户数量可能超过了一亿。另外,2017年年底,有145款谷歌应用程序被恶意的Microsoft Windows操作系统上的可执行文件感染。这些安全事件给开发人员敲响了警钟,因为病毒通过感染开发工具和开发环境,使得开发者成为病毒传播链条上的关键一环。开发环境是软件开发生命周期的关键部分,如果疏于安全防护,那么其他环节的安全对策可能只是徒劳的尝试。

3.4.2 持续集成流水线

对比传统的手工生产模式,持续集成是全自动化过程,高效且不容易出错,因此极大地减少了每次迭代的周期,保证整体项目可以按照极小的步伐和极高的频率进行稳步演进。持续集成流水线(Pipeline)是通过可视化的方式,将持续集成流程的各个步骤按照一定的接力顺序展示并且执行。“流水线”模式可以清晰地展示持续集成执行的过程和各个阶段的状况。

Jenkins是目前最流行的持续集成工具。在老版本的Jenkins(2016年以前发布的)中,通过安装相关的流水线插件,然后将研发流程的各个步骤以Job的形式配置好,再将各个Job按照流程顺序在流水线模块中定义为逐一执行,可以将持续集成的整个过程可视化。从流水线中可以很清楚地看到各个阶段的执行顺序、状态、运行时间等。如果某个阶段的运行出现问题,则其状态会被标注出来(比如红色),方便开发人员锁定问题范围并可以根据相关日志进行分析。图3-5给出了老版本Jenkins流水线的一个例子。开发阶段包括系统构建、单元测试、打包和制品上传等功能;测试阶段包括集成测试和回归测试;发布阶段包括UAT和生产环境的发布。

089-01

图3-5 老版本Jenkins流水线

2016年以后发布的Jenkins新版本(Jenkins 2)将流水线的功能进行了集成,不仅提供了传统配置模式创建流水线的方式,而且提出了全新的“Pipeline as Code”(流水线即代码)的理念,通过在JenkinsFile写脚本的方式创建和编写流水线(图3-6)。这种写代码的方式更容易被程序员接受,也更适合DevOps的模式,即将配置管理工作从运维或者测试阶段左移到开发阶段。另外,Jenkins企业版提供了流水线模板,使得Jenkins流水线的使用更加灵活方便。

089-02

图3-6 Jenkins 2“Pipeline as Code”流水线

因为JenkinsFile本身是一种脚本语言文件,所以可以存储在代码托管工具中进行版本管理。这样做可以将流水线作为代码看待,强制执行良好的规范。关于流水线脚本的命名,最好设置为默认名称jenkinsfile,并且以“#!groovy”脚本开头,这样方便IDE、GitHub等工具将其识别为Groovy并启用代码高亮。在设计编写流水线代码时,流水线内的任何非安装操作都应该在某一个stage内执行,这样方便按逻辑切割流程,将流水线分成清晰的几个步骤。另外,流水线的实质性操作最好放在外接节点中进行,而非Jenkins主机中运行。这样一是避免构建过程中影响到Jenkins主机的状态,二是Jenkins的分布式构建能力可以充分利用外接节点的资源。例如:

stage 'build'
node{
    checkout scm
    sh 'mvn clean install'
//build
stage 'test'
//test

Jenkins流水线提供了一个方法,可以将流水线分成并行的步骤,这种并行分配工作的机制可以让流水线运行更快,并更快地获得开发人员的反馈。同时,在并行步骤中获取并使用节点可以提高并发构建速度。例如:

parallel 'integration-tests':{
    node('mvn-3.6.3'){...}
}, 'functional-tests':{
    node('selenuim'){...}
}

3.4.3 安全能力在流水线上的融入

在上一小节中我们讨论了作为实现持续交付自动化的技术手段:流水线。DevSecOps的核心是实现安全“左移”到研发团队,而不是依赖于安全团队。然而,“左移”的过程不能给程序员或者测试人员增加额外的工作,比如需要程序员操作相关安全工具做安全扫描以及制作安全报告之类的工作。这将给程序员增加额外的负担,加大学习成本,甚至可能引起抵触情绪。

所以说,“左移”也并非简单地将安全工具交给开发和测试团队使用。Neil MacDonald和Ian Head在非常有名的一篇文章《DevSecOps:如何平滑地将安全集成进DevOps》[5]中提到,“信息安全框架必须整合到DevOps的工作流程中,信息安全对于开发和运维工程师来说必须是‘透明且无感知’的,这样才能保证DevOps开发和运维的敏捷和效率”。因此,所谓左移,是不能给开发和测试人员造成任何负担的“平滑、透明、无感知”的接入。这就要求DevSecOps工具具备集成到流水线并且实现自动化的能力,才能让安全“无感”地融入开发流程。另外,安全工具的自动化也解决了DevOps模式下,安全扫描和评审跟不上快速迭代和交付节奏的问题。图3-7给出了DevSecOps在整个研发生命周期各个阶段的集成。

091-01

图3-7 DevSecOps工具在流水线上的融入

在开发阶段,安全测试/扫描可以通过插件将DevSecOps工具和流水线进行集成。相关的代码和第三方安全扫描(SAST&SCA)可以与代码质量分析、单元测试等并行执行,而测试安全扫描(DAST&IAST)也可以在进行功能测试时同步进行。最终的结果在度量平台或者CI平台上进行展示。另外,安全漏洞也可以作为质量门禁的参数,根据不同的安全漏洞等级设置相关的阈值。如果扫描出相关安全漏洞(比如高危安全漏洞),则自动让相关构建失败,迫使程序员修复安全漏洞。使用DevSecOps安全流水线,可以进行低成本的推广和应用,免去整合安全工具的人力和时间投入,释放项目二次开发维护人力。另外,将安全工具集成到DevOps平台和流水线上,可以轻松实现相关安全数据的采集、上传到统一度量平台,形成反馈闭环。在下一小节中,我们将详细介绍DevSecOps中的代码质量和安全分析、扫描,以及对第三方开源软件的安全扫描等。