ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

Terraform模板安全审计实战:从静态扫描到CI/CD门禁的全流程指南

Terraform模板安全审计实战:从静态扫描到CI/CD门禁的全流程指南 1. 从一次安全评审翻车说起测试为什么要管Terraform模板先讲一段真实经历。去年我做某个云上数据平台项目的质量保障功能测试、性能测试都安排得明明白白结果在一次内部安全评审会上被问了一句你们的Terraform模板做过安全审查吗当时我愣了一下。项目里的基础设施代码是运维同事写的按我过去的思路那是别人家的领域。结果对方接着展示了安全团队扫描出来的结果一个S3存储桶的访问策略配置成了*通配符一个安全组的入站规则对全世界开放了SSH端口还有一个数据库实例的备份功能被显式关闭。这些都不是推送后偶然出现的而是从一开始就写在Terraform模板里的定时炸弹。从那以后我开始意识到一件事基础设施即代码IaC是交付链路的一部分只要代码会进CI/CD流水线它就应该和业务代码一样接受自动化质量与安全检查。Terraform作为目前最普及的IaC工具它的模板文件.tf或.tf.json本质上就是一份声明式的资源蓝图。所谓模板安全合规性自动化审计就是通过工具和规则在模板被部署之前识别其中的安全配置缺陷、合规性偏差以及潜在风险点。这篇实践指南会围绕怎么审、用什么审、审完怎么闭环展开适合正在接触基础设施代码、想把安全质量左移的测试从业者、DevOps工程师和平台团队参考。2. 为什么传统测试思维在IaC审计上不灵了2.1 IaC审计和接口测试、UI自动化完全不是一回事很多测试出身的人刚接触Terraform审计时会本能地沿用功能测试的思路先搭环境、再发请求、然后断言结果。这种思路在IaC场景下基本行不通原因很现实。Terraform的apply是一次有真实副作用的操作。你很难为审计一套模板专门搭一套完整的云环境那等于为了检查菜有没有毒先做了一桌菜。更合理的做法是在模板尚未生效的静态阶段直接解析它的声明式结构检查其中是否存在明显的错误或风险配置。另一个差异在于断言对象。接口测试断言的是响应体的字段UI自动化断言的是页面元素的状态而Terraform模板审计的断言对象是资源的属性声明。比如一个aws_s3_bucket资源我要关心的是它的acl属性是否为privateversioning是否开启一个aws_security_group资源我要关心ingress规则里是否有/0这种危险IP段。这种属性级断言更接近代码静态分析而不是传统意义上的黑盒测试。2.2 测试从业者的优势风险建模和用例设计能力不过测试从业者有一个优势是运维或纯开发背景的人未必具备的风险建模能力。IAc审计本质上是一个穷举坏配置可能性的过程这和设计测试用例时穷举坏输入的逻辑高度相似。我做审计规则设计时会先建一张针对每类云资源的风险矩阵列出资源类型、高风险属性、常见违规手段和影响等级。比如资源类型高风险属性常见违规示例影响等级对象存储桶acl / policy允许公共读写严重安全组ingress cidr_blocks对 /0 开放22端口严重数据库实例backup_retention_period关闭备份或保留期过短高IAM策略action / resource使用通配符授权全部操作高负载均衡器access_logs未开启访问日志中一旦风险矩阵建立起来剩下的工作就是不断扩充它并根据业务场景调整优先级。这跟写测试用例时的等价类划分边界值分析思维是同源的。2.3 安全的三个层次静态扫描、策略校验、部署后验证Terraform模板审计并不是单点动作我倾向于把它拆成三个层次。第一层是静态扫描在模板文件层面直接解析资源定义和代码规范检查类似。这一层能做的是发现硬编码的密钥、危险的通配符、未加密的磁盘等显性问题。第二层是策略校验通过OPAOpen Policy Agent这类策略引擎把组织级的合规基线固化成代码。静态扫描关注的是这个配置本身安不安全策略校验关注的是这个配置是否符合我们公司的规范两者会重叠但目的不同。第三层是部署后验证在Terraform成功apply之后通过云平台API或terraform plan的JSON输出做二次确认。为什么需要这一层因为有些配置在模板中是动态的比如使用了变量引用静态扫描无法确定最终值只能等部署后由云平台侧反馈。这三个层次正好对应测试领域的分层策略单元层、集成层、端到端层。想通这一点之后你会觉得IaC审计并没有那么陌生。3. 审计工具链选型我不是只看tfsec第一次找IaC审计工具的时候很多人会先遇到tfsec因为它名气大、规则多、用起来简单。但我实际在项目里跑了一圈之后最终采用的是组合方案这个组合在设计时考虑了几个维度规则的完善度、自定义策略的灵活性、CI集成的便利性以及报表的可读性。选择组合方案时我注意到Checkov对多云场景的支持更好而且拥有更丰富的合规基线如CIS基准、PCI-DSS要求OPA则适合承载那些Checkov内置规则覆盖不到的组织级自定义策略。Terrascan也试过它的强项在于跨云资源关联分析但触发器设计相对简单。以下是工具选择时的横向对比工具规则数量自定义策略CI集成适合场景tfsec中低好快速起步、轻量扫描Checkov多中支持Python好多云合规基线Terrascan中低一般跨资源关系分析OPA/Rego自建高好组织级策略固化3.1 tfsec为什么被我降级为辅助工具tfsec确实很好用单条命令就能扫描整个目录并且会根据错误类型输出对应的CIS控制项编号。但它的短板也很明显规则集是固定的虽然覆盖面广但遇到公司内部特别定制的规范就无能为力了。比如我们要求所有生产环境的存储桶必须启用服务端加密同时必须使用特定类型的KMS密钥这种场景在tfsec内置规则里没有直接对应项。如果只依赖tfsec需要额外的人工检查环节自动化审计的价值就打了折扣。还有一点tfsec更偏扫描对合规基线的梳理不够系统。而我们在做的是安全合规性审计不只是找明显漏洞还要证明这些模板符合某个标准。这需要工具能关联到明确的合规条目Checkov在这方面的沉淀做得更好。3.2 Checkov我把大部分内置规则交给它Checkov是Bridgecrew团队开源的工具语法上直接解析Terraform、CloudFormation、Kubernetes等多种IaC格式。我用它做主力有三个理由。第一它预置的规则数量是同类工具里最丰富的覆盖AWS、Azure、GCP的主流高危配置。第二自定义策略可以直接写Python这对于测试背景的人特别友好因为我本身就有Python基础不用专门学一门新的策略语言。第三它的输出格式支持JSON、SARIF、JUnit XML这对接测试报告体系很方便后面我会细说。使用Checkov扫描一个Terraform目录的命令是checkov --directory ./terraform/envs/prod --framework terraform --output json --download-external-modules true注意--download-external-modules true这个参数。Terraform模板经常会引用公共模块仓库里的module如果不把外部模块拉下来扫描结果会不完整。这个参数在默认情况下是关闭的很多人第一次跑就漏了一大片区域。Checkov扫描完会输出类似这样的结果Check: CKV_AWS_20: Ensure all data stored in S3 bucket is encrypted FAILED for resource: aws_s3_bucket.data_bucket单个FAILED会直接告诉你违反的是哪条规则、影响哪个资源非常直观。3.3 OPA和Rego承载不能写进内置规则的策略有些策略连Checkov的自定义Python接口都未必方便表达比如所有资源的名称必须遵循{项目}-{环境}-{组件}命名规范或者同一VPC内的子网网段不允许重叠。这些跨资源的逻辑用OPA的表达能力更强我通常用OPA来处理这类组织级策略。OPA的输入格式一般是Terraform的plan JSON即先执行terraform plan -outtfplan.binary再通过terraform show -json tfplan.binary tfplan.json拿到完整的资源变更清单然后用Rego策略对这个JSON做评估。package terraform.audit deny[msg] { resource : input.resource_changes[_] resource.mode managed resource.change.after.name not re_match(^[a-z]-[a-z]-[a-z]$, resource.change.after.name) msg : sprintf(resource %s name violates naming convention, [resource.address]) }这就是把合规要求代码化的典型写法。OPA的接入成本比Checkov高一些但它让审计规则完全掌握在自己手里不受制于工具内置规则的边界。4. 审计规则怎么设计从CIS基线到业务场景映射4.1 第一套规则从哪里来对测试从业者来说第一次设计审计规则时最头疼的问题通常是我该检查哪些东西。答案其实不复杂从成熟基线出发再根据实际情况裁剪。我主要参考三个来源。CISCenter for Internet Security基准是最体系化的参照里面有大量云服务商相关的安全配置基准比如CIS AWS Foundations BenchmarkOWASP的API安全Top 10看起来和基础设施无关但不少原则能够映射过来比如权限最小化映射到IAM策略设计资产暴露面最小化映射到安全组和公网IP还有云服务商自身的Well-Architected Framework安全支柱比如AWS的安全支柱文档里面有很多可落地的配置建议。一个实用的做法是先把CIS AWS Foundations Benchmark里和Terraform可声明资源相关的条目抽出来转成Checkov规则再在后续迭代中按公司规范补充自定义条目。4.2 高价值规则的拆解与参数设计我把审计规则分为三类按性价比排序。第一类是一票否决型比如存储桶公共读写、安全组开放高危端口、IAM策略包含所有操作通配符。这类规则一旦触发必须阻断流水线没有商量余地。第二类是默认安全型比如未启用磁盘加密、未开启备份、缺少访问日志。这类规则违反不一定立刻造成数据泄露但会在长期运行中积累风险。我通常把它们配置为warning级别允许合并但要求一周内修复。第三类是规范符合型比如资源命名规范、标签完整性、版本控制启用情况。这类规则更多是管理诉求用OPA承载更合适。高价值规则在设计时最重要的一点是否定式匹配优于肯定式匹配。很控制必须符合某个安全基线不如控制任何不符合安全基线的行为都要被列出。这也是Checkov规则设计的底层逻辑每个规则本质上都是找反例。4.3 一个自定义Checkov规则的完整示例以存储桶服务端加密为例如果公司要求所有S3存储桶必须用指定的KMS密钥加密光靠内置规则检查不到这么细我写一个自定义Checkov规则from checkov.common.models.enums import CheckCategories, CheckResult from checkov.terraform.checks.resource.base_resource_check import BaseResourceCheck class S3BucketKMSEncryption(BaseResourceCheck): def __init__(self): name Ensure S3 bucket is encrypted with specific KMS key id CKV_CUSTOM_001 supported_resources [aws_s3_bucket] categories [CheckCategories.ENCRYPTION] super().__init__(namename, idid, categoriescategories, supported_resourcessupported_resources) def scan_resource_conf(self, conf): if server_side_encryption_configuration in conf: rule conf[server_side_encryption_configuration][0].get(rule, []) if rule and rule[0].get(apply_server_side_encryption_by_default): sse_algorithm rule[0][apply_server_side_encryption_by_default][0].get(sse_algorithm, [aws:kms]) if aws:kms in sse_algorithm: return CheckResult.PASSED return CheckResult.FAILED check S3BucketKMSEncryption()这个自定义规则的检查函数放在scan_resource_conf里直接操作Checkov解析出来的Terraform资源属性字典。如果你熟悉Python的字典操作写这种自定义规则基本没有学习成本。规则写好后放到--custom-checks目录下用--custom-checks path/to/checks_dir参数引入。我建议每个自定义规则都写一段测试用例比如构造一个正常的资源定义和一个违规的资源定义分别验证PASSED和FAILED的返回。这样规则本身也进入自动化测试的范畴避免改了规则之后把正常配置误杀。5. 接入CI/CD流水线的完整过程5.1 审计阶段应该放在流水线的哪个位置审计阶段的位置很讲究。我的经验是放在Terraform validate之后、terraform plan之前因为plan是一个相对昂贵的操作如果模板本身存在明显安全缺陷没必要浪费时间去生成plan。还有一个原因如果放在plan之后虽然可以依赖plan JSON进行更精确的检查但GitLab CI或GitHub Actions里的任务通常能拿到的是源码目录而不是plan产物的地址。放在plan之前可以让每个Merge Request的流水线独立跑审计互不依赖。到了后续迭代阶段我会把审计拆分到两个Job里merge request触发的变更前置审计只扫描本次变更涉及的文件主干分支触发的全量审计扫描完整目录树。变更前置审计在MR阶段拦截明显问题全量审计防止某人绕过MR直接推送到主干。5.2 GitLab CI的配置示例我所在团队用的是GitLab审计Job的配置大致如下terraform-audit: stage: security-check image: name: bridgecrew/checkov:latest entrypoint: [] script: - checkov --directory . --framework terraform \ --download-external-modules true \ --skip-check CKV_GIT_1 \ --output junitxml --output-file-path ./reports/ \ --soft-fail-on CKV_AWS_18,CKV_AWS_23 artifacts: when: always reports: junit: reports/*.xml paths: - reports/这里说几个关键点。--output junitxml把Checkov结果转成JUnit格式GitLab能直接把JUnit报告展示在MR页面测试从业者非常熟悉这种红绿反馈。--soft-fail-on参数很实用它让你能对特定规则设置软失败——即使这些规则FAILED流水线也不会被阻塞但结果会显示在报告中。我通常把那些需要业务方确认才能判断是否违规的规则放进这个列表。--skip-check CKV_GIT_1是因为Checkov针对GitHub配置的规则在GitLab流水线里没有意义直接跳过避免无效的FAILED干扰注意力。5.3 门禁策略什么情况下必须阻断发布门禁设计的核心问题是如何平衡研发效率与安全合规。我经历过两个方向的教训一开始太严任何一条warning都阻断流水线结果是开发同学每天花大量时间处理规则误报甚至开始想办法绕开审计随后另一个项目又太松审计结果只展示不阻断很快就没有人看检查报告了。我现在采用三档门禁模型硬阻断严重级别规则比如CKV_AWS_20、自定义KMS加密规则只要FAILED就直接阻断流水线合并请求也无法通过。限期整改默认安全型规则显示为warning合并允许通过但系统自动创建跟踪任务要求7天内修复。报表展示规范符合型规则只记入审计报表不进入门禁判定逻辑。这个策略的本质是让审计工具从开发流程的阻碍者变成质量数据的生产者只有严重安全问题才真正卡住发布其余问题通过跟踪机制逐步清偿。5.4 全量计划任务定时扫描比只扫MR更可靠MR触发审计只能覆盖增量变更但生产环境里已经应用很久的存量模板未必经过审计。我在项目中加了每周末自动执行的定时审计任务把各环境的Terraform目录全量扫描一遍结果汇总到安全报表。audit-scheduled: stage: security-check image: bridgecrew/checkov:latest script: - checkov --directory . --framework terraform \ --download-external-modules true \ --output json --output-file-path ./reports/ artifacts: paths: - reports/ rules: - if: $CI_PIPELINE_SOURCE schedule定时任务的价值在于它能发现那些一直存在但从未被检查的历史问题。项目刚接入审计的第一周定时扫描就找到了12个存量存储桶未启用版本控制的问题这在MR扫描里是完全不会出现的。6. 踩坑实录这一节是根据实践经验补充的6.1 误报是怎么被发现的单点登录跳板机安全组案例审计上线第一周就有开发同学来找我说Checkov把生产环境跳板机的安全组标记成了FAILED因为入站规则中对跳板机管理端口开放的IP段不满足规则的要求。我打开模板一看ingress规则确实写成了一段公网IP但这段IP是公司办公网出口IP是安全部门专门批准开放的。Checkov内置规则看到非私网IP段就报FAILED但它不理解这是受控的办公网出口IP这个业务上下文。这就是典型的高误报场景。解决方案不是删除规则而是在工具层面把它标记为suppression# .checkov.yml skip-check: - CKV_AWS_24: comment: 跳板机管理入口IP段由安全团队批准开放基于来源票号SEC-2023-1188这段注释很重要因为几个月后你回头看skip清单必须知道当时为什么跳过这条规则。没有注释的suppression和掩盖问题的行为没有区别。6.2 动态值导致的盲区变量引用和lookup函数静态扫描最大的盲区在于处理的模板里充满动态值。我遇到过一个案例存储桶的acl属性写的是var.bucket_aclCheckov默认把它视作未知值选择跳过检查。结果CI里传入的变量值被某个配置覆盖成了public-read问题就漏过去了。这就是为什么我在前面强调部署后验证这个层次。处理动态值问题的通用方案是给变量增加校验条件在Terraform变量定义里通过validation块约束允许值范围。variable bucket_acl { description ACL for the S3 bucket type string validation { condition contains([private, log-delivery-write], var.bucket_acl) error_message bucket_acl must be one of: private, log-delivery-write. } }这样一来即使静态扫描无法确定最终值Terraform本身也在执行前做一次变量合法性校验。多一层校验就少一个盲区。6.3 fork式模块仓库的版本漂移问题Terraform项目用Module很常见但如果Module是从某个上游仓库fork出来的且版本更新滞后也可能引发安全问题。上游Module修复了一个安全漏洞你的项目引用的是老版本Checkov扫描本地模板文件会识别不到。我在流水线里加了一层Module版本检查任务解析.terraform.lock.hcl对比当前Module版本与上游最新版本差异。如果差异中包含安全修复类commit会在MR上提示更新。这个任务不贵但对长期维护的项目价值极大。7. 从审计结果到安全闭环报表、缺陷跟踪与修复验证7.1 用SARIF和JUnit把审计结果接入测试平台审计结果如果只躺在CI日志里价值接近于零。我在实践中接触过多种输出方案最终选定两套主流格式JUnit接入GitLab的MR报告SARIF接入代码安全平台。SARIF是静态分析结果交换的开放标准GitHub Security Code Scanning直接支持它在GitLab里也可以通过API对接展示结果。生成SARIF的Checkov参数非常简单checkov --directory ./terraform --framework terraform --output sarif --output-file-path ./reports/checkov.sarif这样做有一个额外好处安全团队的安全扫描平台可以消费SARIF格式把IaC扫描结果和上下文信息合并到统一的风险面板中不必让开发者到处去翻多套工具的界面和报表。测试团队拿到的是能直插入研发流程的结果而不只是一次性报告。7.2 自动创建缺陷跟踪单对限期整改级别的问题我写了一个脚本解析Checkov的JSON输出识别其中的warning级别FAILED条目按资源地址和规则ID自动去重然后调用缺陷管理系统的API创建缺陷单。每个缺陷单会附上资源文件路径、检查规则ID、修复建议链接并指派给对应代码模块的负责人。这个环节建议放在定时审计任务里做而不是每次MR都触发否则会产生大量重复工单。等缺陷单创建之后MR审计里的同类warning会被引用到已有工单编号开发者一眼就能看到这是已知问题。7.3 修复验证用同样规则做回归测试修复验证的逻辑和功能测试天然一致修复前扫描一次修复后扫描一次比对同一规则ID的检查结果是否翻转。我直接用Checkov的基线功能实现checkov --directory ./terraform --framework terraform --baseline .checkov.baseline在修复前先生成基线文件之后每次扫描只报告新增问题。修复完成后重新生成基线确认问题清零或者已进入suppression列表。这个流程会让审计工作形成一个完整循环发现问题、记录问题、修复问题、验证修复而不是每次扫描都从零开始面对一张没有上下文的大清单。8. 实例复盘一次完整的存储桶策略修复过程为了让你对这套流程有直观印象我拆一个真实复盘的例子。某次定时审计发现prod-data-platform目录下的S3存储桶资源触发了自定义规则CKV_CUSTOM_001原因是模板里只配了AES256加密没有按要求使用指定的KMS密钥。审计任务自动在GitLab MR页面展示了JUnit格式的失败信息并创建了缺陷单指派给该模块的负责人。开发同学查看缺陷单后在模板中做了如下修改resource aws_s3_bucket data_bucket { bucket prod-data-platform-${var.region}-${var.env} server_side_encryption_configuration { rule { apply_server_side_encryption_by_default { kms_master_key_id aws_kms_key.data_key.arn sse_algorithm aws:kms } } } }然后提交了新的MR。流水线里的Checkov阶段再次运行CKV_CUSTOM_001通过了但同时触发了另一条规则CKV_AWS_19要求该KMS密钥必须开启自动轮转。开发同学继续补上resource aws_kms_key data_key { enable_key_rotation true }到这里所有安全规则通过MR才合入主干之后terraform apply正常执行。这个简单案例展示了完整闭环是怎么运作的自动化发现缺陷、自动跟踪到责任人、开发者按反馈修复、工具再次验证。整个过程不需要专门召开安全评审会也不需要安全团队凑时间逐个人工看模板。9. 给测试从业者的行动建议如果看了前面的内容你准备在团队里落地这套实践我先给三个实际建议每一个都是踩过坑之后换来的经验。第一点先从只扫不改开始。不要第一天就设计复杂的门禁规则而是先接入Checkov或tfsec把扫描结果以报告形式发出来让团队看到问题清单再决定修正优先级和是否阻塞流水线。上来就直接硬阻断会让开发团队对安全审计产生很强的逆反心理。第二点规则集要瘦身而非加码。默认全开的规则集会产生大量低价值FAILED学会根据项目实际情况关闭无关规则只保留那些确实匹配业务风险的条目审计反馈才会真正被关注。我开始跑规则的时候全量打开结果一轮扫描产生了两位数的误报条目光解释就花了半天得不偿失。第三点尽量把修复建议写进报告里。工具输出文档只是列出了哪个资源哪个规则失败了这还不够。在团队里给每条常用规则配上精准的修复指引开发者看到后直接照做这样才能让审计成为正能量而不是新的负担。Checkov有--output bc选项可以输出对应的Bridgecrew平台修复链接GitLab里可以直接引用这些链接。这个方向后续可以走的路还有不少在Terraform module仓库层面做统一的安全基线管理用OPA把公司内部的网络分区规范做成自动策略甚至结合云平台的安全中心API做实时的配置漂移检测。本质上Terraform模板的审计和功能测试、性能测试、安全渗透测试一样都是在交付链路上收紧质量闸门。测试从业者天然具备系统化找问题、验证修复、持续迭代的能力这正是基础设施审计最需要的思维方式。
返回列表