:Kustomize/Helm 渲染器文件归属判定的安全基石)
Kubescape 路径包含检查Path ContainmentKustomize/Helm 渲染器文件归属判定的安全基石【免费下载链接】kubescapeKubescape is an open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters. It includes risk analysis, security, compliance, and misconfiguration scanning, saving Kubernetes users and administrators precious time, effort, and resources.项目地址: https://gitcode.com/GitHub_Trending/ku/kubescapeKubescape 在扫描本地目录、Git 仓库或 Helm Chart 时其资源加载器resource loader需要反复回答同一个问题某个源文件是否归属于某个渲染器如 Kustomize、Helm所拥有的目录。本指南以仓库中的 docs/path-containment.md 为核心结合core/cautils/fileutils.go、core/pkg/resourcehandler/filesloader.go与core/pkg/fixhandler的源码与测试完整讲解路径包含判定Path Containment Checks的动机、别名处理symlink 与相对路径、目录感知目录级而非字符串前缀级判定、以及kubescape fix修复阶段的路径逃逸防护帮助你掌握 Kubescape 如何避免同一资源被重复扫描、如何防止恶意仓库通过符号链接把修复写入扫描目录之外。一、为什么需要路径包含检查Kubescape 支持对本地目录、文件、远程 Git 仓库、Helm Chart 与 Kustomize 目录进行扫描。当一个输入目录中同时存在普通 YAML 清单、Helm Chart 模板和 Kustomize 配置时加载流程必须保证每个资源只被正确的一方处理一次Kustomize 目录一旦构建成功其渲染产物与原始输入就不应再以普通清单身份被重复扫描Helm Chart 一旦被渲染器展开templates/下的模板文件也不应再作为独立清单被读取若同一资源同时以渲染后与原始两种身份进入扫描结果会造成重复告警、错误的资源归属和混乱的报告。因此Kubescape 的资源加载器需要判定一个源文件是否位于某个渲染器所拥有的目录之下。当渲染器成功时该渲染器目录之下的原始输入不应再被当作普通清单二次扫描。这正是文档 docs/path-containment.md 所定义的路径包含检查要解决的核心问题。从源码看这一判定被封装为共享辅助函数cautils.IsUnderAnyDir定义于 core/cautils/fileutils.go所有文件系统归属决策都应优先复用它而不是在局部各自写strings.HasPrefix判断——后者正是文档末尾强调的工程约束。二、核心判定函数IsUnderAnyDir 与 dirSetIsUnderAnyDir是文档所述共享辅助函数的实现// IsUnderAnyDir reports whether path is inside one of dirs after normalizing // relative paths and resolving symlinks where the path already exists. func IsUnderAnyDir(path string, dirs []string) bool { return newDirSet(pathAliasesForPaths(dirs)).containsAnyAlias(path) }其内部由三个部件协同工作core/cautils/fileutils.gopathAliasesForPaths(dirs)为每个候选目录生成别名集合词法绝对路径 解析后的物理路径newDirSet(...)将归一化后的目录构建为map[string]struct{}集合dirSet类型成员先经过filepath.Clean因此尾部斜杠、.段、冗余/都不会破坏匹配containsAnyAlias(path)对目标路径的每个别名从该路径开始沿祖先链逐级向上查找filepath.Dir只要命中集合中的任一目录即判定包含。目录级判定而非字符串前缀判定dirSet.contains采用祖先行走ancestor walk而非字符串前缀比较core/cautils/fileutils.gofunc (s dirSet) contains(path string) bool { if len(s) 0 { return false } for dir : path; ; { if _, ok : s[dir]; ok { return true } parent : filepath.Dir(dir) if parent dir { return false } dir parent } }这个设计直接落实了文档中的两个断言app/service.yaml位于app之下 →包含app-docs/policy.yaml与app仅是字符串前缀兄弟 →不包含。从源码注释可见该实现替换了原先每个目录做一次filepath.Rel相对路径比较的朴素做法dirSet构建一次后可被大量路径复用单次查找的代价从目录数 × 相对路径计算降为路径深度级专门服务于一个目录下数千个文件对多个渲染目录的仓库扫描形态。测试 core/cautils/fileutils_test.go 的TestDirSetContains明确覆盖了这些语义包括共享前缀的兄弟目录不包含空集合不含任何内容根目录/包含一切绝对路径等边界。三、路径别名处理符号链接与词法/物理路径差异文档特别强调包含检查必须考虑路径别名。例如在 macOS 等系统上/tmp/repo/app/deployment.yaml与/private/tmp/repo/app/deployment.yaml可能指向同一文件/tmp是/private/tmp的符号链接文件系统遍历器walker可能保留词法路径而渲染器或辅助函数返回的是符号链接解析后的物理路径。IsUnderAnyDir对源路径和候选目录两侧都做别名比较。别名由pathAliases生成core/cautils/fileutils.gofunc pathAliases(path string) []string { absPath, err : filepath.Abs(path) if err ! nil { return []string{filepath.Clean(path)} } absPath filepath.Clean(absPath) resolvedPath, err : filepath.EvalSymlinks(absPath) if err ! nil || resolvedPath absPath { return []string{absPath} } return []string{absPath, resolvedPath} }要点先经filepath.Abs转为绝对路径并Clean保证相对路径与./、../段得到归一再用filepath.EvalSymlinks解析符号链接得到物理路径若解析失败路径尚不存在例如 Kustomize 尚未拉取的远端 Chart或解析结果与词法路径相同则只保留一个别名词法路径与物理路径都保留对符号链接的模板文件Helm 按templates/下的词法位置拥有它即使链接目标在 Chart 之外而物理形式仍能匹配经由符号链接父目录到达的扫描路径。代码注释中还明确了缺失路径的处理原则canonicalPathcore/cautils/fileutils.go远端 Chart 的路径可能尚不存在此时保留绝对词法形式即可因为通用发现流程在文件真正存在之前不会返回匹配路径。测试TestIsUnderAnyDir_CanonicalizesSymlinkscore/cautils/fileutils_test.go验证了通过符号链接父目录访问的linked-parent/app/deployment.yaml其物理布局与候选目录realParent/app一致时仍判定为包含。四、生产调用点排除重复扫描的渲染器目录路径包含检查在资源加载链路中的实际调用点是excludeFilesUnderDirectoriescore/pkg/resourcehandler/filesloader.gofunc excludeFilesUnderDirectories(sourceToWorkloads map[string][]workloadinterface.IMetadata, dirs []string) { if len(dirs) 0 { return } for source : range sourceToWorkloads { if cautils.IsUnderAnyDir(source, dirs) { delete(sourceToWorkloads, source) } } }其调用上下文getResourcesFromPathcore/pkg/resourcehandler/filesloader.go完整呈现了文档所述场景先渲染 Kustomize 目录LoadResourcesFromKustomizeDirectoriesFiltered记录构建成功的目录再渲染 Helm ChartsLoadResourcesFromHelmChartsFiltered并传入 Kustomize 已拥有的 Chart 目录避免被 Kustomize 构建覆盖的 Chart 又被当作独立 Helm Release 渲染最终把已渲染的 Chart 目录renderedCharts与Kustomize 拥有的 Chart 目录合并为coveredChartDirectories传给excludeFilesUnderDirectories从普通清单结果中剔除位于这些目录之下的源文件。这样一来渲染成功目录下的原始输入不会再以普通清单身份进入扫描结果从根源上消除了同一资源的渲染态 原始态双重身份问题。这就是文档开头所描述的When a renderer succeeds, raw inputs below that renderer-owned directory should not be scanned again as plain manifests.五、路径包含在修复阶段的强化kubescape fix 的路径逃逸防护路径包含检查不止服务于扫描去重还直接关系到kubescape fix的写入安全。core/pkg/fixhandler中有一套与IsUnderAnyDir语义呼应的本地防护相关测试集中在 core/pkg/fixhandler/pathcontainment_test.go。5.1 词法相对路径检查的局限isPathContained早期仅做词法相对路径比较测试用例明确了它的基本语义core/pkg/fixhandler/pathcontainment_test.go相对路径..穿越到基准目录之外 → 拒绝绝对路径落在基准目录之外 → 拒绝基准目录之下的普通子路径 → 接受目标等于基准目录本身 → 接受目标文件不存在 → 拒绝不允许静默放行。5.2 符号链接逃逸的回归修复测试注释明确记录了评审中发现的两处漏洞core/pkg/fixhandler/pathcontainment_test.gofilepath.Rel是纯词法的——基准目录内指向外部的符号链接曾经被报告为包含即使通过它写入会落在被扫描目录之外。修复后isPathContained必须解析符号链接新增用例包括基准目录内的符号链接指向外部 → 拒绝TestIsPathContained的 symlink inside base pointing outside it is rejected基准目录内的符号链接指向内部 → 接受悬空符号链接dangling symlink→ 拒绝。这些用例在 Windows 上会被skipOnWindowsSymlink跳过Windows 下创建符号链接需要提升权限CI 运行器可能不具备这是平台差异的合理取舍。5.3 端到端防护PrepareResourcesToFix 跳过逃逸资源更高层的回归测试TestPrepareResourcesToFix_SymlinkEscapeIsSkippedcore/pkg/fixhandler/pathcontainment_test.go复现了第二个评审漏洞扫描目录内的符号链接指向外部时修复流程曾通过符号链接把改动写入被扫描树之外的文件——即使报告与--base-path完全可信例如扫描一个包含此类符号链接的第三方克隆仓库。修复后行为PrepareResourcesToFix返回空的待修复列表资源被标记为unfixed原因字符串为skipped: resource path escapes scanned directory同时测试断言外部victim.yaml内容保持不变证明写入被有效拦截。5.4 --base-path 锚定报告路径kubescape fix的NewFixHandler在设置--base-path时还会校验报告自身记录的扫描位置core/pkg/fixhandler/pathcontainment_test.go未设置--base-path保留既有行为信任报告自身记录的扫描位置无论报告文件位于何处设置了--base-path报告中的basePath必须解析在调用方提供的根目录之内否则报错并拒绝创建 handler错误信息包含outside --base-path--base-path本身无效路径不存在也会报错invalid --base-path报告路径等于--base-path、或位于其子目录下 → 接受报告路径通过符号链接别名指向--base-path内 → 接受且保留报告中的词法拼写TestNewFixHandler_BasePathFlag_PreservesAcceptedReportBasePathSpelling断言h.localBasePath保留别名拼写而非物理路径。这一设计的价值在于恶意报告不能再通过自行设置basePath把kubescape fix指向任意目录——它必须落在调用方信任的根目录之内。六、测试矩阵与工程约定文档要求测试覆盖五类场景仓库测试与之一一对应文档要求的场景对应测试断言位置直接子路径direct child pathsTestIsUnderAnyDirfile directly inside directorycore/cautils/fileutils_test.go前缀兄弟prefix siblingsTestIsUnderAnyDirsibling sharing a prefix is outside同上文件系统根包含TestIsUnderAnyDirevery path is under the root directory同上词法源路径在物理目录之下TestIsUnderAnyDir_CanonicalizesSymlinkscore/cautils/fileutils_test.go物理源路径在词法目录之下pathAliases同时保留词法/物理别名core/cautils/fileutils.go文档还给出了两条明确的工程约定仓库实现完全遵循优先使用共享辅助函数做文件系统归属决策而不是局部strings.HasPrefix检查——因为后者无法正确处理app与app-docs的前缀兄弟问题也无法处理符号链接别名构建一次、复用多次dirSet应针对同一组目录构建一次并复用大批量路径检查时才能获得祖先行走的低成本优势BenchmarkExcludeHelmTemplateFilescore/cautils/fileutils_test.go模拟了 200 个渲染 Chart 目录对 2000 个清单文件的真实扫描形态。七、总结Kubescape 的路径包含检查是一套小而关键的安全与正确性机制贯穿扫描加载与修复写入两个阶段扫描阶段cautils.IsUnderAnyDir借助dirSet祖先行走实现目录感知的包含判定通过词法/物理双别名正确处理符号链接与相对路径确保 Kustomize/Helm 渲染成功的目录下的原始输入不被重复扫描修复阶段fixhandler的isPathContained在词法检查基础上解析符号链接阻止通过链接把修复写入扫描目录之外--base-path又把报告自述的扫描位置锚定在调用方信任的根目录内。如果你正在为 Kubescape 贡献加载器或修复逻辑涉及某个文件是否属于某目录的决策时请直接复用cautils.IsUnderAnyDir或其对应的 fixhandler 内部检查并参照 core/cautils/fileutils_test.go 与 core/pkg/fixhandler/pathcontainment_test.go 中的用例补齐直接子路径、前缀兄弟、根目录、双向符号链接别名等场景的测试。【免费下载链接】kubescapeKubescape is an open-source Kubernetes security platform for your IDE, CI/CD pipelines, and clusters. It includes risk analysis, security, compliance, and misconfiguration scanning, saving Kubernetes users and administrators precious time, effort, and resources.项目地址: https://gitcode.com/GitHub_Trending/ku/kubescape创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考