ARTICLE DETAIL

资讯详情

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

Checkov Terraform Plan 测试数据中的“非预期变换”现象:以 `aws_eks_node_group.remote_access` 为案例的深入剖析

Checkov Terraform Plan 测试数据中的“非预期变换”现象:以 `aws_eks_node_group.remote_access` 为案例的深入剖析 Checkov Terraform Plan 测试数据中的“非预期变换”现象以aws_eks_node_group.remote_access为案例的深入剖析【免费下载链接】checkovPrevent cloud misconfigurations and find vulnerabilities during build-time in infrastructure as code, container images and open source packages with Checkov by Bridgecrew.项目地址: https://gitcode.com/GitHub_Trending/ch/checkov导读本文围绕 tests/terraform/runner/resources/unexpected/unexpected.md 这份测试数据说明文档展开讲解 Checkov 在解析 Terraform Plan JSON 时的一类特殊现象HCL 源码中未书写的属性在 Plan 的 JSON 表示中会被 Terraform 以空集合如remote_access: []的形式填充。文章将结合仓库中的真实测试数据、Plan 解析源码与安全检查实现说明这种非预期变换为何会发生、对安全检查意味着什么以及如何在编写 Check 时规避此类问题。一、unexpected目录的定位测试 Plan Runner 的意外输入1.1 目录存在的意义在 Checkov 的 Terraform 测试体系中tests/terraform/runner/resources/unexpected/ 是一个专门存放非预期场景测试数据的目录。目录说明文档 unexpected.md 明确指出其用途This folder is for different cases of test runner test data where the input HCL is maybe unexpectedly transformed when you see the json representation.翻译过来即该目录存放的是这样一类 runner 测试数据——输入 HCL 在转换为 JSON 表示时可能发生了意料之外的变换。这一设计背后是 Checkov 的一个核心特性Checkov 既能直接扫描.tf的 HCL 源码也能扫描terraform plan导出的tfplan.json。后者由 checkov/terraform/plan_runner.py 驱动其file_extensions [.json]表明它只处理 JSON 格式的 plan 文件。由于 Plan JSON 是 Terraform 对配置求值后的产物其数据结构与源 HCL 并非一一对应因此会出现源码里没有、JSON 里却有的字段。1.2 为什么需要这样一个专门目录文档接着强调This area can be used to verify that certain checks are robust in catching issues which cant be caught by unit testing the HCL input level alone.也就是说仅对 HCL 输入层面做单元测试是不够的。某些问题只有在 Plan 转换后的形态下才会暴露因此需要这份独立的测试数据来验证检查器Check在 Plan 场景下的健壮性。这正是 Checkov 采用多层次测试策略的体现HCL 层面的单元测试 Plan 层面的 runner 集成测试。二、案例剖析eks_node_group_remote_access2.1 现象描述目录中目前唯一一个具体案例是eks_node_group_remote_access。文档给出的描述非常精确remote_accessis omitted in HCL. But is represented asremote_access: [ ]in the Plan. This needs to be taken in to account when writing the check.核心结论有两点HCL 中省略了remote_access块——开发者在写资源时根本没有声明该属性但在 Plan 的 JSON 输出中该属性被表示为空数组remote_access: []——Terraform 在序列化计划时补齐了未设置的可选属性并以空集合占位。第二点正是非预期变换的具象体现属性的存在性presence与属性的值value被 Terraform 重构了Check 作者在编写规则时必须把这种形态考虑进去。2.2 HCL 输入案例的 HCL 输入是一个典型的 EKS 节点组资源注意其中没有remote_access块resource aws_eks_node_group test { cluster_name test node_group_name example node_role_arn example-arn subnet_ids [subnet-ids] scaling_config { desired_size 1 max_size 1 min_size 1 } }2.3 JSON 输出remote_access: []的完整上下文对应的 Plan JSON 保存在 tests/terraform/runner/resources/unexpected/eks_node_group_remote_access.json。这份 JSON 是 Terraform 0.14.6 生成的 planformat_version: 0.1其中三个区块都体现了remote_access被填充为空数组在planned_values.root_module.resources[0].values中{ cluster_name: test, force_update_version: null, labels: null, launch_template: [], node_group_name: example, node_role_arn: example-arn, remote_access: [], scaling_config: [{ desired_size: 1, max_size: 1, min_size: 1 }], subnet_ids: [subnet-ids], tags: null, timeouts: null }在resource_changes[0].change.after中同样出现remote_access: []同时after_unknown中也有remote_access: []after: { cluster_name: test, remote_access: [], scaling_config: [{ desired_size: 1, max_size: 1, min_size: 1 }], subnet_ids: [subnet-ids] }, after_unknown: { ami_type: true, arn: true, remote_access: [], resources: true }而configuration.root_module.resources[0].expressions中则完全没有remote_access键——这与 HCL 源码一致反向印证了源码省略、JSON 填充的结论expressions: { cluster_name: { constant_value: test }, node_group_name:{ constant_value: example }, node_role_arn: { constant_value: example-arn }, scaling_config: [{ desired_size: { constant_value: 1 }, max_size: { constant_value: 1 }, min_size: { constant_value: 1 } }], subnet_ids: { constant_value: [subnet-ids] } }三、源码级验证Plan 解析器如何制造空集合要理解remote_access: []的成因需要回到 Checkov 的 Plan 解析器 checkov/terraform/plan_parser.py。3.1 资源块的组装入口parse_tf_planplan_parser.py#L507遍历planned_values.root_module.resources为每个资源找到configuration中对应的配置块然后调用_prepare_resource_block组装出可供 Check 扫描的资源配置字典plan_parser.py#L176resource_conf _hclify( objresource.get(values, {start_line: 0, end_line: 0}), confexpressions, resource_typeresource_type, )关键点在于_hclify的输入对象直接取自planned_values中 Terraform 序列化出的values而 Terraform 在values中会把所有可选属性都铺开未设置者为null或空集合。因此remote_access: []会原样进入 Checkov 的资源配置字典。3.2_hclify的默认空集合逻辑_hclify的实现plan_parser.py#L129-L130对配置中缺失的键采用了空集合兜底策略child_list [] conf_val conf.get(key, []) if conf else []从源码结构看当某个属性在expressions即原始配置中不存在、但在求值后的values中存在时解析器会使用配置侧的空列表参与合并处理。这进一步说明Checkov 在设计上允许资源配置中出现属性存在但为空集合的形态Check 代码必须对这种形态保持容错。3.3after_unknown的补充处理_prepare_resource_block还会调用_eval_after_unknownplan_parser.py#L228读取resource_changes中的after_unknown。该函数只在环境变量EVAL_TF_PLAN_AFTER_UNKNOWN开启时生效并仅当某个字段值为True表示 apply 后才可知且当前配置中不存在该字段时才写入占位值true_after_unknown。由于remote_access在after_unknown中是空列表而非True因此不会被写入占位值——它仍然以[]的形态存在。也就是说空集合与未知值占位是两条独立的处理路径Check 作者需要分别应对。四、对应 Check 的健壮写法CKV_AWS_100 实测4.1 检查规则本体负责该场景的检查是CKV_AWS_100实现在 checkov/terraform/checks/resource/aws/EKSNodeGroupRemoteAccess.pyclass EKSNodeGroupRemoteAccess(BaseResourceCheck): def __init__(self): name Ensure AWS EKS node group does not have implicit SSH access from 0.0.0.0/0 id CKV_AWS_100 supported_resources [aws_eks_node_group] categories [CheckCategories.KUBERNETES] super().__init__(namename, idid, categoriescategories, supported_resourcessupported_resources) def scan_resource_conf(self, conf): remote_access conf.get(remote_access) if remote_access and remote_access[0] and ec2_ssh_key in remote_access[0].keys() \ and source_security_group_ids not in remote_access[0].keys(): return CheckResult.FAILED return CheckResult.PASSED def get_evaluated_keys(self) - List[str]: return [remote_access/[0]/ec2_ssh_key, remote_access/[0]/source_security_group_ids]4.2 三段式防御如何消化空数组该检查的判定逻辑只有一行却做了三层防御恰好一一化解了remote_access: []带来的风险防御条件表达式应对的 Plan 形态属性是否存在remote_access andconf.get()返回NoneHCL 直接扫描场景列表是否非空remote_access[0] andremote_access []本文的 Plan 场景是否包含 SSH 密钥键ec2_ssh_key in remote_access[0].keys()空字典{}或其他缺键形态只有当三个条件同时满足——即remote_access非空、首个元素存在、且包含ec2_ssh_key却缺少source_security_group_ids——才会判定为 FAILED。这意味着在 HCL 源码扫描中如果资源里压根没有remote_access块conf.get(remote_access)返回None检查通过PASSED语义正确没有 SSH 配置也就没有隐式 SSH 暴露在 Plan JSON 扫描中即使remote_access被填充为[]remote_access[0]对空列表求值为假falsy同样通过不会产生误报。这正是文档所强调的writing the check时需要考虑的形态差异如果不做remote_access[0]这一层非空判断直接对[]取下标就会抛异常或产生错误的布尔逻辑导致整个扫描任务崩溃或误报。4.3 既有 HCL 层测试的不足checkov/terraform/checks/resource/aws/ 下的既有测试通常以 HCL 源码或直接构造的配置字典为输入很难覆盖属性被 Terraform 变换为空数组这种形态。这正是 unexpected.md 中cant be caught by unit testing the HCL input level alone论断的源码依据Plan 层面的形态变换必须在 Plan runner 的集成测试数据中显式验证。五、集成测试如何验证该场景5.1 测试用例test_plan_runner.py 中的test_runner_unexpected_eks_node_group_remote_access直接消费这份 JSONdef test_runner_unexpected_eks_node_group_remote_access(self): current_dir os.path.dirname(os.path.realpath(__file__)) valid_plan_path current_dir /resources/unexpected/eks_node_group_remote_access.json runner Runner() report runner.run( root_folderNone, files[valid_plan_path], external_checks_dirNone, runner_filterRunnerFilter(framework[all]), ) report_json report.get_json() self.assertIsInstance(report_json, str) self.assertIsNotNone(report_json) self.assertIsNotNone(report.get_test_suite()) self.assertEqual(report.get_exit_code({soft_fail: False, soft_fail_checks: [], soft_fail_threshold: None, hard_fail_checks: [], hard_fail_threshold: None}), 0) self.assertEqual(report.get_exit_code({soft_fail: True, soft_fail_checks: [], soft_fail_threshold: None, hard_fail_threshold: None}), 0) self.assertEqual(report.get_summary()[failed], 0) self.assertEqual(report.get_summary()[passed], 1)5.2 断言解读该测试的断言清晰地传达了预期的行为failed 0且passed 1对这个省略了remote_access的资源CKV_AWS_100 必须判定为通过1 条 passed不能产生任何失败项。这直接验证了空数组不触发误报的健壮性要求soft_fail与hard_fail两种模式下 exit code 均为 0无论采用软失败还是硬失败策略整个扫描都不应因这个 Plan 文件而退出非零确保 runner 流程稳定get_json()返回合法字符串、test suite 非空确认 Plan runner 在解析该 JSON 后报告结构完整、可正常序列化。整个用例形成了一条完整的证据链非预期变换的 JSON 数据 → Plan runner 解析 → 检查执行 → 断言无失败闭环验证了文档描述的场景。六、对 Check 编写者的工程启示综合文档、源码与测试可以沉淀出三条可复用的编写经验属性存在性 ≠ 属性被设置。Terraform Plan 会对省略的可选属性填充null或[]Check 中所有通过conf.get(key)取到的值都必须额外做非空判断后再访问内部结构如remote_access[0]。可参考 EKSNodeGroupRemoteAccess.py 中if remote_access and remote_access[0] and ...的链式防御写法。新增针对 Plan 形态的测试数据时把用例放入unexpected目录并同步编写 runner 级断言。仅靠 HCL 输入层面的单元测试无法覆盖属性被变换为空数组这类问题这正是 unexpected.md 建立的测试约定断言时建议同时校验failed/passed计数与soft_fail/hard_fail两种退出码参考 test_plan_runner.py#L517 的完整写法。区分三类形态conf.get(key)返回NoneHCL 中确实没有、返回[]Plan 中填充的空集合、返回[ {...} ]真正配置了内容。Check 逻辑应分别给出明确的通过/失败语义避免把未配置误判为不安全配置或直接崩溃。总结unexpected目录及其eks_node_group_remote_access案例是 Checkov 在 Terraform Plan 扫描场景下对输入形态鲁棒性的一次刻意设计Terraform 会把 HCL 中省略的属性在 JSON Plan 中变换为remote_access: []而 Checkov 通过 plan_parser.py 的解析管线原样保留这一形态再依赖 CKV_AWS_100 的三段式防御在运行时安全消化。这套文档说明 测试数据 集成断言三位一体的做法既是 Checkov 自身测试策略的体现也为所有基于 Terraform Plan 做静态扫描的开发者提供了一份可复用的最佳实践模板。【免费下载链接】checkovPrevent cloud misconfigurations and find vulnerabilities during build-time in infrastructure as code, container images and open source packages with Checkov by Bridgecrew.项目地址: https://gitcode.com/GitHub_Trending/ch/checkov创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表