标签 Java 下的文章

Go的简洁性之辩:轻量级匿名函数提案为何七年悬而未决?

本文永久链接 – https://tonybai.com/2025/06/03/lightweight-anonymous-func-syntax

大家好,我是Tony Bai。

自2017年提出以来,Go语言关于引入轻量级匿名函数语法的提案(Issue #21498)一直是社区讨论的焦点。该提案旨在提供一种更简洁的方式来定义匿名函数,尤其是当函数类型可以从上下文推断时,从而减少样板代码,提升代码的可读性和编写效率。然而,历经七年多的广泛讨论、多种语法方案的提出与激辩,以及来自核心团队成员的实验与分析,截至 2025年5 月底,官方对该提案的最新立场是“可能被拒绝 (likely declined)”,尽管问题仍保持开放以供未来考虑。近期该issue又冲上Go issue热度榜,让我有了对该提案做一个简单解读的冲动。在本文中,我将和大家一起探讨该提案的核心动机、社区的主要观点与分歧、面临的挑战,以及这一最新倾向对 Go 语言和开发者的潜在影响。

冗余之痛:当前匿名函数的困境

在Go中,匿名函数的标准写法是

func(参数列表) (返回类型列表) {
    函数体
}

虽然这种语法明确且一致,但在许多场景下,尤其是作为回调函数或在函数式编程风格(如配合泛型和迭代器使用)中,参数和返回类型往往可以从上下文清晰推断,此时显式声明则显得冗余。

提案发起者 Neil (neild) 给出了一个经典的例子:

func compute(fn func(float64, float64) float64) float64 {
    return fn(3, 4)
}

// 当前写法,类型声明重复
var _ = compute(func(a, b float64) float64 { return a + b })

许多现代语言,如 Scala ((x, y) => x + y 或 _ + _) 和 Rust (|x, y| { x + y }),都提供了更简洁的 lambda 表达式语法,允许在类型可推断时省略它们。这种简洁性被认为可以提高代码的信噪比,让开发者更专注于业务逻辑。

Go匿名函数常见的痛点场景包括:

  • 回调函数:如 http.HandlerFunc、errgroup.Group.Go、strings.TrimFunc。
  • 泛型辅助函数:随着 Go 1.18 泛型的引入,如 slices.SortFunc、maps.DeleteFunc 以及设想中的 Map/Filter/Reduce 等操作,匿名函数的应用更加广泛,其冗余性也更为凸显。
  • 迭代器:Go 1.23 引入的 range over func 迭代器特性,也使得将函数作为序列或转换器传递成为常态,轻量级匿名函数能显著改善其体验(如 #61898 x/exp/xiter 提案的讨论中多次提及)。正如一些开发者指出的,结合迭代器使用时,现有匿名函数语法会使代码显得冗长。

提案核心:轻量级语法的设想

该提案的核心思想是引入一种“非类型化函数字面量 (untyped function literal)”,其类型可以从赋值上下文(如变量赋值、函数参数传递)中推断得出。提案初期并未限定具体语法,而是鼓励社区探讨各种可能性。

Go team的AI 生成的总结指出,讨论中浮现的语法思路主要可以归为以下几种:

  1. 箭头函数风格 (Arrow Function Style): 借鉴 JavaScript, Scala, C#, Java 等。

    • 例如:(x, y) => { x + y } 或 (x,y) => x+y
  2. 保留 func 关键字并进行变体:

    • 例如:func a, b { a+b } (省略参数括号)
    • func(a,b): a+b (使用冒号分隔)
    • func { x, y | return x < y } (参数列表移入花括号,使用 | 或 -> 分隔)
  3. 基于现有语法的类型推断改进:

    • 例如:允许在 func(a _, b _) _ { return a + b } 中使用 _ 作为类型占位符。

其核心优势在于:

  • 减少样板代码: 省略冗余的类型声明。
  • 提升可读性(对部分人而言): 使代码更紧凑,逻辑更突出。
  • 促进函数式编程风格: 降低使用高阶函数和回调的心理门槛。

社区的激辩:争议焦点与权衡

该提案引发了 Go 社区长达数年的激烈讨论,根据 Robert Griesemer 提供的 AI上述总结 和整个讨论链,主要争议点包括:

1. 可读性 vs. 简洁性

  • 支持简洁方: 认为在类型明确的上下文中,重复声明类型是视觉噪音。简洁的语法能让代码更易于速读和理解,尤其是在函数式链式调用中。他们认为 Go 已经通过 := 接受了类型推断带来的简洁性。
  • 强调显式方: 以 Dave Cheney 的名言“Clear is better than clever” 为代表,一些开发者认为显式类型声明增强了代码的自文档性和可维护性。他们担心过度省略类型信息会增加认知负担,尤其对于初学者或在没有强大 IDE 支持的情况下阅读代码。Go密码学前负责人FiloSottile 指出,在阅读不熟悉的代码时,缺少类型信息会迫使其跳转到定义或依赖 IDE。Go元老Ian Lance Taylor也表达了对当前显式语法的肯定,认为其对读者而言清晰度很高。

2. 语法选择的困境

这是提案迟迟未能落地的最主要原因之一。社区提出了数十种不同的语法变体,但均未能形成压倒性的共识。

箭头语法 (=> 或 ->):

  • 优点: 许多开发者因在其他语言中的使用经验而感到熟悉,被认为非常简洁。Jimmy Frasche 的语言调查显示这是许多现代语言的选择。
  • 缺点: 一些人认为它“不像 Go”,=> 可能与 >= 或 <= 在视觉上产生混淆,-> 可能与通道操作 <- 混淆 。Robert Griesemer指出,虽然 (x, y) => x + y 感觉自然,但 (x, y) => { … } 对于 Go 而言感觉奇怪。Ian Lance Taylor也表达了对箭头符号的不完全满意,认为在某些代码上下文中可读性欠佳。

保留 func 并简化:

  • func params {} (省略参数括号):Ian Lance Taylor 和 Robert Griesemer 曾探讨过此形式。主要问题在于 func a, b {} 在函数调用参数列表中可能与多个参数混淆。
  • func { params | body } 或 func { params -> body }:Griesemer 在后期倾向于这种将参数列表置于花括号内的形式,认为 func { 可以明确指示轻量级函数字面量。| 用于语句体,-> (可选地) 用于单表达式体。Jimmy Frasche 对此形式的“DSL感”提出异议,认为其借鉴的 Smalltalk/Ruby 风格在 Go 中缺乏相应的上下文。

其他符号:

如使用冒号 func(a,b): expr ,或 _ 作为类型占位符。Griesemer认为 _ 作为类型占位符会产生混淆。

Robert Griesemer 进行的实验表明,func 后不带括号的参数列表 (func x, y { … }) 在实际 Go 代码中看起来奇怪,而箭头符号 (=>) 则“出乎意料地可读”。他后期的实验进一步对比了 (args) => { … } 和 func { args | … }。

3. 隐式返回 (Implicit Return)

对于单表达式函数体是否应该省略 return 关键字,也存在分歧。

  • 支持方: 认为这能进一步提升简洁性,是许多 lambda 语法的常见特性。
  • 反对方: 担心这会使返回行为不够明确,尤其是在 Go 允许多值返回和 ExpressionStmt (如函数调用本身可作为语句) 的情况下,可能会导致混淆或意外行为。例如 func { s -> fmt.Println(s) },如果 fmt.Println 有返回值,这个函数是返回了那些值,还是一个 void 函数?这需要非常明确的规则,并且可能依赖上下文。

4. 类型推断的复杂性与边界

虽然核心思想是“从上下文复制类型”,但当涉及到泛型时,推断会变得复杂。

  • Map((x) => { … }, []int{1,2,3}) :如果 Map 是 func Map[Tin, Tout any](in []Tin, f func(Tin) Tout) []Tout,那么 Tout 如何推断?是要求显式实例化 Map[int, ReturnType],还是尝试从 lambda 体内推断?后者将引入更复杂的双向类型推断,可能导致参数顺序影响推断结果,或在接口类型和具体类型之间产生微妙的 bug(如 typed nil 问题)。
  • neild 和 Merovius 指出,在很多情况下,可能需要显式提供泛型类型参数,或者接受推断的局限性。Griesemer提出的最新简化方案 (params) { statements } 明确指出其类型是从目标函数类型“复制”而来,且目标类型不能有未解析的类型参数。

5. 对 Go 语言哲学的影响

一些开发者担忧,引入过于灵活或“魔法”的语法会偏离 Go 语言简单、直接、显式优于隐式的核心哲学。他们认为现有语法虽冗长,但足够清晰,且 IDE 工具(如 gopls 的自动补全)已在一定程度上缓解了编写时的痛点。

开发者tmaxmax在其详尽的实验分析中指出,尽管标准库中单表达式函数字面量比例不高,但在其工作代码库中,这类情况更为常见,尤其是在使用泛型辅助函数如 Map、Filter 时。这表明不同代码库和使用场景下,对简洁语法的需求度可能存在差异。

最新动向:为何“可能被拒绝”?

在提案的最新comment说明中 (May 2025),明确指出:

The Go team has decided to not proceed with adding a lightweight anonymous function syntax at this time. The complexity cost associated with the new syntax, combined with the lack of clear consensus on the syntax, makes it difficult to justify moving forward. Therefore, this proposal is likely declined for now. The issue will remain open for future consideration, but the Go team does not intend to pursue this proposal for now.

这一立场由 Robert Griesemer 在上述AI 总结中进一步确认。核心原因可以归纳为:

  1. 缺乏明确共识: 尽管讨论热烈,但社区和核心团队均未就一个理想的、被广泛接受的语法方案达成一致。各种方案都有其支持者和反对者,以及各自的优缺点和潜在问题。
  2. 复杂性成本: 任何新语法都会增加语言的复杂性(学习、实现、工具链维护、文档等)。在收益不明确或争议较大的情况下,Go 团队倾向于保守。
  3. 潜在的微妙问题与可读性担忧: 正如讨论中浮现的各种边界情况(如类型推断与泛型的交互、隐式返回的歧义、私有类型访问限制等),引入新语法需要非常谨慎。Ian Lance Taylor 明确表达了对当前显式语法在可读性方面的肯定,并对省略类型信息可能带来的阅读障碍表示担忧。
  4. 已有工具的缓解作用: 正如一些评论者指出,IDE 的自动补全功能在一定程度上减轻了编写冗长函数字面量的痛苦。

Robert Griesemer进一步总结,将备选方案缩小到 (params) { statements }, (params) { statements }, 和 (params) -> { statements } (或 =>),并指出即使是这些方案,也各有其不完美之处。他强调了在没有明确压倒性优势方案和社区强烈共识的情况下,贸然推进的风险。

影响与未来展望

尽管 #21498 提案目前大概率会被搁置,但它所反映的开发者对于减少样板代码、提升特定场景下编码效率的诉求是真实存在的。

  • 对迭代器和泛型库的影响: 如果提案最终未被采纳,那么严重依赖回调函数的泛型库(如设想中的 xiter 或其他函数式集合库)在使用上将保持当前的冗余度。这可能会在一定程度上抑制纯函数式风格在 Go 中的发展,或者促使开发者寻求其他模式(例如,手写循环或构建更专门的辅助函数)。有开发者认为缺乏简洁的 lambda 语法是阻碍 Go 社区充分实验函数式特性(尤其是迭代器组合)的先决条件之一。

  • 社区的持续探索: 提案的开放状态意味着未来仍有讨论空间。如果 Go 语言在其他方面(如类型系统、元编程能力)发生演进,或者社区就某一特定语法方向形成更强共识,提案可能会被重新激活。tmaxmax 建议将讨论重心从无休止的语法细节转向更根本的动机和语义问题。

  • 工具的进步: IDE 和代码生成工具可能会继续发展,以进一步缓解手动编写完整函数字面量的繁琐。

  • 开发者习惯: Go 开发者将继续在现有语法框架内寻求平衡。对于高度重复的匿名函数模式,可能会更多地采用具名辅助函数或方法来封装。正如 adonovan 的实验所示,某些特定场景(如单 return 语句)可能更容易找到局部优化方案。

小结

Go 语言轻量级匿名函数语法的提案 #21498,是一场关于语言简洁性、可读性、一致性与演进方向的深刻大讨论。它暴露出在追求更现代编程范式便利性的同时,维护 Go 语言核心设计哲学的内在张力。虽然目前看来,由于缺乏明确共识和对复杂性的审慎态度,引入一种全新的、被广泛接受的简洁匿名函数语法道阻且长,但这场长达七年的讨论本身,已经为 Go 社区积累了宝贵的思考、实验数据和经验。未来,无论此提案走向何方,对代码清晰度和开发者体验的追求都将持续驱动 Go 语言的演进。Go 团队将持续观察语言的使用和社区的需求,在合适的时机可能会重新审视此类提案。


在 Go 语言的演进过程中,每一个提案的讨论都凝聚了社区的智慧和对这门语言深沉的热爱。轻量级匿名函数语法的提案,历经七年风雨,虽然目前官方倾向于搁置,但这扇门并未完全关闭。

对于 Go 开发者来说,这场旷日持久的讨论留下了哪些值得我们深思的问题?

  • 你认为在当前 Go 的语法体系下,匿名函数的冗余是亟待解决的痛点吗?或者你认为现有的显式声明更符合 Go 的哲学?
  • 在可读性、简洁性和语言复杂性之间,你认为 Go 应该如何权衡?
  • 如果未来 Go 语言采纳某种形式的轻量级匿名函数,你最期待哪种语法特性(例如,类型推断、隐式返回、特定符号)?
  • 你是否在自己的项目中因为匿名函数的冗余而选择过其他编码模式?欢迎分享你的经验和看法。

我期待在评论区看到你的真知灼见,共同探讨 Go 语言的现在与未来!

img{512x368}


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

透视软件供应链安全:SBOM标准解读与Go项目生成指南

本文永久链接 – https://tonybai.com/2025/05/22/go-sbom-practice

大家好,我是Tony Bai。

近年来,软件供应链安全事件频发,从 SolarWinds 到 Log4Shell,每一次都给业界敲响了警钟。在这样的背景下,软件物料清单 (SBOM, Software Bill of Materials) 的重要性日益凸显。无论是甲方爸爸的硬性要求(尤其是在2B软件交付和白盒交付场景),还是我们自身对软件透明度和安全性的追求,SBOM 都已成为现代软件开发不可或缺的一环。

那么,SBOM 究竟是什么?它为何如此重要?市面上有哪些主流的 SBOM 标准?我们又该如何为自己的 Go 项目(当然,也适用于 Java、JS 等其他语言项目)生成和使用 SBOM 呢?

今天,我们就来一起深入探讨这些问题,为你揭开 SBOM 的神秘面纱。

SBOM:你的软件“配料表”,为何如此重要?

想象一下,我们购买食品时会关注配料表,了解其成分、产地和营养信息。SBOM 之于软件,就如同食品的配料表。它是一份正式的、结构化的清单,详细列出了构成某个软件产品的所有组件及其依赖关系。

SBOM 的核心价值在于提升软件供应链的透明度和可管理性,从而增强安全性:

  1. 透明度与可追溯性: 清晰展示软件由哪些“零件”(开源库、第三方组件、内部模块等)组装而成,包括直接依赖和传递依赖,让软件的构成不再是“黑盒”。
  2. 高效的漏洞管理: 当某个组件爆出新的安全漏洞时,通过 SBOM 可以快速定位所有受影响的软件产品,及时采取修复或缓解措施,大大缩短应急响应时间。
  3. 许可证合规性审计: 准确识别所有组件的开源许可证类型,确保符合合规要求,避免潜在的法律风险。
  4. 供应链风险评估: 了解组件的来源、版本、维护状态等信息,有助于评估整个软件供应链的潜在风险。
  5. 提升软件质量与可信度: 向客户和合作伙伴提供 SBOM,能够证明你对软件安全和质量的重视,建立信任。

可以说,SBOM 是构筑现代软件供应链安全防线的基石。

SBOM 标准巡礼:SPDX、CycloneDX、SWID 与 DSDX

要让 SBOM 真正发挥作用,统一的标准至关重要。目前,业界存在多个 SBOM 标准,各有侧重。我们重点关注几个主流和新兴的规范:

1. SPDX (Software Package Data Exchange):

  • 定位与特点:Linux Foundation 主导,是国际公认的 SBOM 开放标准 (ISO/IEC 5962:2021)。SPDX 最初更侧重于许可证合规性,但其规范已发展得非常全面,能够详细描述软件包、文件、代码片段及其之间的关系。
  • 核心数据字段: 包含包信息(名称、版本、供应商、下载位置、校验和)、文件信息(名称、类型、许可证、校验和)、许可证信息(SPDX许可证列表中的标准标识符、自定义许可证)、以及组件之间的关系(依赖、包含、生成等)。
  • 格式: 支持多种格式,如 Tag-Value、JSON、YAML、RDF/XML 等。
  • 适用场景: 许可证合规审计、知识产权管理、软件溯源、大型复杂项目的详细清单管理、漏洞管理等。由于其全面性和国际标准化地位,SPDX 是本次我们实战演练的重点。

2. CycloneDX:

  • 定位与特点:OWASP(开放式Web应用程序安全项目) 社区驱动,更侧重于安全用例和运营需求。它设计轻量、易于自动化生成和消费,非常适合在 CI/CD 流程中集成。
  • 核心数据字段: 关注组件(名称、版本、供应商、PURL、CPE)、依赖关系图谱、已知漏洞信息(或指向漏洞数据库的链接如 VEX)、服务信息、许可证信息等。
  • 格式: 主要支持 JSON 和 XML。
  • 适用场景: 漏洞管理、安全审计、软件成分分析 (SCA)、CI/CD 集成等。

3. SWID (Software Identification) Tags:

  • 定位与特点:ISO/IEC 19770-2:2015 标准定义,主要用于软件资产管理 (SAM) 和安全。SWID 标签提供了识别已安装软件、追踪其生命周期(安装、更新、卸载)的方法。
  • 核心价值: 虽然 SWID 本身不直接提供完整的依赖关系图谱,但它可以作为 SBOM 中组件身份识别的重要依据,并能与其他 SBOM 格式结合使用。
  • 适用场景: 软件资产管理、安全配置管理、补丁管理。

4. DSDX (Digital Supply-chain Data Exchange):

  • 定位与特点: 这是由中国信息通信研究院(CAICT)牵头,联合国内多家单位共同研究制定的数字供应链数据交换标准。它旨在规范数字供应链中各类数据的描述、交换和共享,SBOM 是其关注的重要数据类型之一。
  • 核心价值: DSDX 致力于构建符合中国国情和产业发展需求的数字供应链标准体系,推动国内软件供应链的透明化和安全保障。
  • 适用场景: 国内企业间的软件供应链数据交换、合规性要求等。目前 DSDX 仍在发展和推广阶段,值得国内开发者关注。

标准之间的关系与选择:

这些标准并非完全孤立。例如,SPDX 和 CycloneDX 都被广泛用于生成 SBOM,并且都符合美国 NTIA《软件物料清单的最小元素》的要求。SWID 标签可以增强 SBOM 中组件的识别能力。DSDX 则可能在未来成为国内数字供应链数据交换的重要规范。

在实际操作中,SPDX 和 CycloneDX 是目前最主流的 SBOM 格式选择。 许多工具都支持生成这两种格式,它们之间也可以进行一定程度的转换。本次,我们将以 SPDX 为例进行后续的实战演示。

Go 项目 SPDX SBOM 生成实战:利器 anchore/syft 登场

理论说了不少,我们来动手实践一下。市面上有许多优秀的 SBOM 生成工具,今天我们选用一款广受欢迎的开源工具:anchore/syft

syft 是一个功能强大的 CLI 工具和 Go 库,可以从容器镜像和文件系统中生成 SBOM。它支持多种 SBOM 格式(包括我们今天重点关注的 SPDX 和另一种主流格式 CycloneDX),并且对多种编程语言和包管理器有良好的支持。

安装 syft

你可以从其 GitHub Release 页面下载预编译的二进制文件,或者使用 Go 工具安装:

$go install github.com/anchore/syft/cmd/syft@latest

确保你的 \$GOPATH/bin 或 \$GOBIN 在你的 PATH 环境变量中。

实战:为知名 Go Web 框架 gin-gonic/gin 生成 SPDX JSON SBOM

让我们以一个真实的、大家熟知的 Go 开源项目 gin-gonic/gin 为例。首先,你需要将项目克隆到本地:

$git clone https://github.com/gin-gonic/gin.git
$cd gin

然后,在 gin 项目的根目录下,运行 syft 命令生成 SPDX JSON 格式(spdx 2.3规范)的 SBOM:

$syft . -o spdx-json=gin-sbom.spdx.json
 ✔ Indexed file system                                                                                                        .
 ✔ Cataloged contents                                          cdb4ee2aea69cc6a83331bbe96dc2caa9a299d21329efb0336fc02a82e1839a8
   ├── ✔ Packages                        [48 packages]
   ├── ✔ Executables                     [0 executables]
   ├── ✔ File digests                    [4 files]
   └── ✔ File metadata                   [4 locations]
[0000]  WARN no explicit name and version provided for directory source, deriving artifact ID from the given path (which is not id
... ...

这里的“.”代表当前目录。syft会自动识别 Go 项目的 go.mod 文件来解析依赖,并将结果输出到 gin-sbom.spdx.json 文件中。

注:截至目前,spdx的最新规范版本为3.0.1

生成的 gin-sbom.spdx.json 文件内容片段示例:

{
    "spdxVersion": "SPDX-2.3",
    "dataLicense": "CC0-1.0",
    "SPDXID": "SPDXRef-DOCUMENT",
    "name": ".",
    "documentNamespace": "https://anchore.com/syft/dir/453d49c6-8063-46f1-9d7e-61dd7e789f6d",
    "creationInfo": {
        "licenseListVersion": "3.25",
        "creators": [
            "Organization: Anchore, Inc",
            "Tool: syft-[not provided]"
        ],
        "created": "2025-05-17T22:45:19Z"
    },
    "packages": [
        {
            "name": "actions/cache",
            "SPDXID": "SPDXRef-Package-github-action-actions-cache-422933d2a61f8d51",
            "versionInfo": "v4",
            "supplier": "Organization: GitHub",
            "originator": "Organization: GitHub",
            "downloadLocation": "NOASSERTION",
            "filesAnalyzed": false,
            "sourceInfo": "acquired package info from GitHub Actions workflow file or composite action file: /.github/workflows/gin.yml",
            "licenseConcluded": "NOASSERTION",
            "licenseDeclared": "NOASSERTION",
            "copyrightText": "NOASSERTION",
            "externalRefs": [
                {
                    "referenceCategory": "SECURITY",
                    "referenceType": "cpe23Type",
                    "referenceLocator": "cpe:2.3:a:actions\\/cache:actions\\/cache:v4:*:*:*:*:*:*:*"
                },
                {
                    "referenceCategory": "PACKAGE-MANAGER",
                    "referenceType": "purl",
                    "referenceLocator": "pkg:github/actions/cache@v4"
                }
            ]
        },
... ...

SPDX JSON 格式详细记录了文档信息、包信息(包括名称、版本、SPDXID、许可证、PURL等)以及它们之间的依赖关系。

syft输出定制

如果你觉得syft默认输出到json文件中的信息不全,你可以对syft的行为做一些配置,可以使用syft配置文件,也可以使用环境变量。

syft默认的配置文件位置有如下几个(优先级从高到低):

.syft.yaml
.syft/config.yaml
~/.syft.yaml
<XDG_CONFIG_HOME>/syft/config.yaml

如果你不知道配置文件的格式,可以执行syft config查看当前配置:

$syft config
log:
  # suppress all logging output (env: SYFT_LOG_QUIET)
  quiet: false

  # increase verbosity (-v = info, -vv = debug) (env: SYFT_LOG_VERBOSITY)
  verbosity: 0

  # explicitly set the logging level (available: [error warn info debug trace]) (env: SYFT_LOG_LEVEL)
  level: 'warn'

  # file path to write logs to (env: SYFT_LOG_FILE)
  file: ''

dev:
  # capture resource profiling data (available: [cpu, mem]) (env: SYFT_DEV_PROFILE)
  profile: ''

# the configuration file(s) used to load application configuration (env: SYFT_CONFIG)
config: ''

# the output format(s) of the SBOM report (options: syft-table, syft-text, syft-json, spdx-json, ...)
# to specify multiple output files in differing formats, use a list:
# output:
#   - "syft-json=<syft-json-output-file>"
#   - "spdx-json=<spdx-json-output-file>" (env: SYFT_OUTPUT)
output:
  - 'syft-table'

# file to write the default report output to (default is STDOUT) (env: SYFT_LEGACYFILE)
legacyFile: ''

format:
  # default value for all formats that support the "pretty" option (default is unset) (env: SYFT_FORMAT_PRETTY)
  pretty:

  template:
    # path to the template file to use when rendering the output with the template output format.
    # Note that all template paths are based on the current syft-json schema (env: SYFT_FORMAT_TEMPLATE_PATH)
    path: ''

    # if true, uses the go structs for the syft-json format for templating.
    # if false, uses the syft-json output for templating (which follows the syft JSON schema exactly).
    #
    # Note: long term support for this option is not guaranteed (it may change or break at any time) (env: SYFT_FORMAT_TEMPLATE_LEGACY)
    legacy: false

  json:
    # transform any syft-json output to conform to an approximation of the v11.0.1 schema. This includes:
    # - using the package metadata type names from before v12 of the JSON schema (changed in https://github.com/anchore/syft/pull/1983)
    #
    # Note: this will still include package types and fields that were added at or after json schema v12. This means
    # that output might not strictly be json schema v11 compliant, however, for consumers that require time to port
    # over to the final syft 1.0 json output this option can be used to ease the transition.
    #
    # Note: long term support for this option is not guaranteed (it may change or break at any time) (env: SYFT_FORMAT_JSON_LEGACY)
    legacy: false

    # include space indentation and newlines
    # note: inherits default value from 'format.pretty' or 'false' if parent is unset (env: SYFT_FORMAT_JSON_PRETTY)
    pretty:

  spdx-json:
    # include space indentation and newlines
    # note: inherits default value from 'format.pretty' or 'false' if parent is unset (env: SYFT_FORMAT_SPDX_JSON_PRETTY)
    pretty:

  cyclonedx-json:
    # include space indentation and newlines
    # note: inherits default value from 'format.pretty' or 'false' if parent is unset (env: SYFT_FORMAT_CYCLONEDX_JSON_PRETTY)
    pretty:

  cyclonedx-xml:
    # include space indentation and newlines
    # note: inherits default value from 'format.pretty' or 'false' if parent is unset (env: SYFT_FORMAT_CYCLONEDX_XML_PRETTY)
    pretty:

# whether to check for an application update on start up or not (env: SYFT_CHECK_FOR_APP_UPDATE)
check-for-app-update: true

# enable one or more package catalogers (env: SYFT_CATALOGERS)
catalogers: []

# set the base set of catalogers to use (defaults to 'image' or 'directory' depending on the scan source) (env: SYFT_DEFAULT_CATALOGERS)
default-catalogers: []

# add, remove, and filter the catalogers to be used (env: SYFT_SELECT_CATALOGERS)
select-catalogers: []

package:
  # search within archives that do not contain a file index to search against (tar, tar.gz, tar.bz2, etc)
  # note: enabling this may result in a performance impact since all discovered compressed tars will be decompressed
  # note: for now this only applies to the java package cataloger (env: SYFT_PACKAGE_SEARCH_UNINDEXED_ARCHIVES)
  search-unindexed-archives: false

  # search within archives that do contain a file index to search against (zip)
  # note: for now this only applies to the java package cataloger (env: SYFT_PACKAGE_SEARCH_INDEXED_ARCHIVES)
  search-indexed-archives: true

  # allows users to exclude synthetic binary packages from the sbom
  # these packages are removed if an overlap with a non-synthetic package is found (env: SYFT_PACKAGE_EXCLUDE_BINARY_OVERLAP_BY_OWNERSHIP)
  exclude-binary-overlap-by-ownership: true

license:
  # include the content of licenses in the SBOM for a given syft scan; valid values are: [all unknown none] (env: SYFT_LICENSE_CONTENT)
  content: 'none'

  # deprecated: please use 'license-content' instead (env: SYFT_LICENSE_INCLUDE_UNKNOWN_LICENSE_CONTENT)
  include-unknown-license-content:

  # adjust the percent as a fraction of the total text, in normalized words, that
  # matches any valid license for the given inputs, expressed as a percentage across all of the licenses matched. (env: SYFT_LICENSE_COVERAGE)
  coverage: 75

  # deprecated: please use 'coverage' instead (env: SYFT_LICENSE_LICENSE_COVERAGE)
  license-coverage:

file:
  metadata:
    # select which files should be captured by the file-metadata cataloger and included in the SBOM.
    # Options include:
    #  - "all": capture all files from the search space
    #  - "owned-by-package": capture only files owned by packages
    #  - "none", "": do not capture any files (env: SYFT_FILE_METADATA_SELECTION)
    selection: 'owned-by-package'

    # the file digest algorithms to use when cataloging files (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512") (env: SYFT_FILE_METADATA_DIGESTS)
    digests:
      - 'sha1'
      - 'sha256'

  content:
    # skip searching a file entirely if it is above the given size (default = 1MB; unit = bytes) (env: SYFT_FILE_CONTENT_SKIP_FILES_ABOVE_SIZE)
    skip-files-above-size: 256000

    # file globs for the cataloger to match on (env: SYFT_FILE_CONTENT_GLOBS)
    globs: []

  executable:
    # file globs for the cataloger to match on (env: SYFT_FILE_EXECUTABLE_GLOBS)
    globs: []

# selection of layers to catalog, options=[squashed all-layers deep-squashed] (env: SYFT_SCOPE)
scope: 'squashed'

# number of cataloger workers to run in parallel
# by default, when set to 0: this will be based on runtime.NumCPU * 4, if set to less than 0 it will be unbounded (env: SYFT_PARALLELISM)
parallelism: 0

relationships:
  # include package-to-file relationships that indicate which files are owned by which packages (env: SYFT_RELATIONSHIPS_PACKAGE_FILE_OWNERSHIP)
  package-file-ownership: true

  # include package-to-package relationships that indicate one package is owned by another due to files claimed to be owned by one package are also evidence of another package's existence (env: SYFT_RELATIONSHIPS_PACKAGE_FILE_OWNERSHIP_OVERLAP)
  package-file-ownership-overlap: true

compliance:
  # action to take when a package is missing a name (env: SYFT_COMPLIANCE_MISSING_NAME)
  missing-name: 'drop'

  # action to take when a package is missing a version (env: SYFT_COMPLIANCE_MISSING_VERSION)
  missing-version: 'stub'

# Enable data enrichment operations, which can utilize services such as Maven Central and NPM.
# By default all enrichment is disabled, use: all to enable everything.
# Available options are: all, golang, java, javascript (env: SYFT_ENRICH)
enrich: []

dotnet:
  # only keep dep.json packages which an executable on disk is found. The package is also included if a DLL is found for any child package, even if the package itself does not have a DLL. (env: SYFT_DOTNET_DEP_PACKAGES_MUST_HAVE_DLL)
  dep-packages-must-have-dll: false

  # only keep dep.json packages which have a runtime/resource DLL claimed in the deps.json targets section (but not necessarily found on disk). The package is also included if any child package claims a DLL, even if the package itself does not claim a DLL. (env: SYFT_DOTNET_DEP_PACKAGES_MUST_CLAIM_DLL)
  dep-packages-must-claim-dll: true

  # treat DLL claims or on-disk evidence for child packages as DLL claims or on-disk evidence for any parent package (env: SYFT_DOTNET_PROPAGATE_DLL_CLAIMS_TO_PARENTS)
  propagate-dll-claims-to-parents: true

  # show all packages from the deps.json if bundling tooling is present as a dependency (e.g. ILRepack) (env: SYFT_DOTNET_RELAX_DLL_CLAIMS_WHEN_BUNDLING_DETECTED)
  relax-dll-claims-when-bundling-detected: true

golang:
  # search for go package licences in the GOPATH of the system running Syft, note that this is outside the
  # container filesystem and potentially outside the root of a local directory scan (env: SYFT_GOLANG_SEARCH_LOCAL_MOD_CACHE_LICENSES)
  search-local-mod-cache-licenses:

  # specify an explicit go mod cache directory, if unset this defaults to $GOPATH/pkg/mod or $HOME/go/pkg/mod (env: SYFT_GOLANG_LOCAL_MOD_CACHE_DIR)
  local-mod-cache-dir: '~/Go/pkg/mod'

  # search for go package licences in the vendor folder on the system running Syft, note that this is outside the
  # container filesystem and potentially outside the root of a local directory scan (env: SYFT_GOLANG_SEARCH_LOCAL_VENDOR_LICENSES)
  search-local-vendor-licenses:

  # specify an explicit go vendor directory, if unset this defaults to ./vendor (env: SYFT_GOLANG_LOCAL_VENDOR_DIR)
  local-vendor-dir: ''

  # search for go package licences by retrieving the package from a network proxy (env: SYFT_GOLANG_SEARCH_REMOTE_LICENSES)
  search-remote-licenses:

  # remote proxy to use when retrieving go packages from the network,
  # if unset this defaults to $GOPROXY followed by https://proxy.golang.org (env: SYFT_GOLANG_PROXY)
  proxy: 'https://goproxy.cn,direct'

  # specifies packages which should not be fetched by proxy
  # if unset this defaults to $GONOPROXY (env: SYFT_GOLANG_NO_PROXY)
  no-proxy: 'gomod.io,10.170.133.199'

  main-module-version:
    # look for LD flags that appear to be setting a version (e.g. -X main.version=1.0.0) (env: SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_LD_FLAGS)
    from-ld-flags: true

    # search for semver-like strings in the binary contents (env: SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_CONTENTS)
    from-contents: false

    # use the build settings (e.g. vcs.version & vcs.time) to craft a v0 pseudo version
    # (e.g. v0.0.0-20220308212642-53e6d0aaf6fb) when a more accurate version cannot be found otherwise (env: SYFT_GOLANG_MAIN_MODULE_VERSION_FROM_BUILD_SETTINGS)
    from-build-settings: true

java:
  # enables Syft to use the network to fetch version and license information for packages when
  # a parent or imported pom file is not found in the local maven repository.
  # the pom files are downloaded from the remote Maven repository at 'maven-url' (env: SYFT_JAVA_USE_NETWORK)
  use-network:

  # use the local Maven repository to retrieve pom files. When Maven is installed and was previously used
  # for building the software that is being scanned, then most pom files will be available in this
  # repository on the local file system. this greatly speeds up scans. when all pom files are available
  # in the local repository, then 'use-network' is not needed.
  # TIP: If you want to download all required pom files to the local repository without running a full
  # build, run 'mvn help:effective-pom' before performing the scan with syft. (env: SYFT_JAVA_USE_MAVEN_LOCAL_REPOSITORY)
  use-maven-local-repository:

  # override the default location of the local Maven repository.
  # the default is the subdirectory '.m2/repository' in your home directory (env: SYFT_JAVA_MAVEN_LOCAL_REPOSITORY_DIR)
  maven-local-repository-dir: '~/.m2/repository'

  # maven repository to use, defaults to Maven central (env: SYFT_JAVA_MAVEN_URL)
  maven-url: 'https://repo1.maven.org/maven2'

  # depth to recursively resolve parent POMs, no limit if <= 0 (env: SYFT_JAVA_MAX_PARENT_RECURSIVE_DEPTH)
  max-parent-recursive-depth: 0

  # resolve transient dependencies such as those defined in a dependency's POM on Maven central (env: SYFT_JAVA_RESOLVE_TRANSITIVE_DEPENDENCIES)
  resolve-transitive-dependencies: false

javascript:
  # enables Syft to use the network to fill in more detailed license information (env: SYFT_JAVASCRIPT_SEARCH_REMOTE_LICENSES)
  search-remote-licenses:

  # base NPM url to use (env: SYFT_JAVASCRIPT_NPM_BASE_URL)
  npm-base-url: ''

  # include development-scoped dependencies (env: SYFT_JAVASCRIPT_INCLUDE_DEV_DEPENDENCIES)
  include-dev-dependencies:

linux-kernel:
  # whether to catalog linux kernel modules found within lib/modules/** directories (env: SYFT_LINUX_KERNEL_CATALOG_MODULES)
  catalog-modules: true

nix:
  # enumerate all files owned by packages found within Nix store paths (env: SYFT_NIX_CAPTURE_OWNED_FILES)
  capture-owned-files: false

python:
  # when running across entries in requirements.txt that do not specify a specific version
  # (e.g. "sqlalchemy >= 1.0.0, <= 2.0.0, != 3.0.0, <= 3.0.0"), attempt to guess what the version could
  # be based on the version requirements specified (e.g. "1.0.0"). When enabled the lowest expressible version
  # when given an arbitrary constraint will be used (even if that version may not be available/published). (env: SYFT_PYTHON_GUESS_UNPINNED_REQUIREMENTS)
  guess-unpinned-requirements: false

registry:
  # skip TLS verification when communicating with the registry (env: SYFT_REGISTRY_INSECURE_SKIP_TLS_VERIFY)
  insecure-skip-tls-verify: false

  # use http instead of https when connecting to the registry (env: SYFT_REGISTRY_INSECURE_USE_HTTP)
  insecure-use-http: false

  # Authentication credentials for specific registries. Each entry describes authentication for a specific authority:
  # -   authority: the registry authority URL the URL to the registry (e.g. "docker.io", "localhost:5000", etc.) (env: SYFT_REGISTRY_AUTH_AUTHORITY)
  #     username: a username if using basic credentials (env: SYFT_REGISTRY_AUTH_USERNAME)
  #     password: a corresponding password (env: SYFT_REGISTRY_AUTH_PASSWORD)
  #     token: a token if using token-based authentication, mutually exclusive with username/password (env: SYFT_REGISTRY_AUTH_TOKEN)
  #     tls-cert: filepath to the client certificate used for TLS authentication to the registry (env: SYFT_REGISTRY_AUTH_TLS_CERT)
  #     tls-key: filepath to the client key used for TLS authentication to the registry (env: SYFT_REGISTRY_AUTH_TLS_KEY)
  auth: []

  # filepath to a CA certificate (or directory containing *.crt, *.cert, *.pem) used to generate the client certificate (env: SYFT_REGISTRY_CA_CERT)
  ca-cert: ''

# specify the source behavior to use (e.g. docker, registry, oci-dir, ...) (env: SYFT_FROM)
from: []

# an optional platform specifier for container image sources (e.g. 'linux/arm64', 'linux/arm64/v8', 'arm64', 'linux') (env: SYFT_PLATFORM)
platform: ''

source:
  # set the name of the target being analyzed (env: SYFT_SOURCE_NAME)
  name: ''

  # set the version of the target being analyzed (env: SYFT_SOURCE_VERSION)
  version: ''

  # base directory for scanning, no links will be followed above this directory, and all paths will be reported relative to this directory (env: SYFT_SOURCE_BASE_PATH)
  base-path: ''

  file:
    # the file digest algorithms to use on the scanned file (options: "md5", "sha1", "sha224", "sha256", "sha384", "sha512") (env: SYFT_SOURCE_FILE_DIGESTS)
    digests:
      - 'SHA-256'

  image:
    # allows users to specify which image source should be used to generate the sbom
    # valid values are: registry, docker, podman (env: SYFT_SOURCE_IMAGE_DEFAULT_PULL_SOURCE)
    default-pull-source: ''

    # (env: SYFT_SOURCE_IMAGE_MAX_LAYER_SIZE)
    max-layer-size: ''

# exclude paths from being scanned using a glob expression (env: SYFT_EXCLUDE)
exclude: []

unknowns:
  # remove unknown errors on files with discovered packages (env: SYFT_UNKNOWNS_REMOVE_WHEN_PACKAGES_DEFINED)
  remove-when-packages-defined: true

  # include executables without any identified packages (env: SYFT_UNKNOWNS_EXECUTABLES_WITHOUT_PACKAGES)
  executables-without-packages: true

  # include archives which were not expanded and searched (env: SYFT_UNKNOWNS_UNEXPANDED_ARCHIVES)
  unexpanded-archives: true

cache:
  # root directory to cache any downloaded content; empty string will use an in-memory cache (env: SYFT_CACHE_DIR)
  dir: '~/Library/Caches/syft'

  # time to live for cached data; setting this to 0 will disable caching entirely (env: SYFT_CACHE_TTL)
  ttl: '7d'

# show catalogers that have been de-selected (env: SYFT_SHOW_HIDDEN)
show-hidden: false

attest:
  # the key to use for the attestation (env: SYFT_ATTEST_KEY)
  key: ''

  # password to decrypt to given private key
  # additionally responds to COSIGN_PASSWORD env var (env: SYFT_ATTEST_PASSWORD)
  password: ''

也可将输出的当前配置保存为上面配置文件中的任何一个,然后做配置定制。

此外,我们看到对于每个重要的配置,都会有一个环境变量对应,比如:

SYFT_FORMAT_SPDX_JSON_PRETTY - spdx json格式美化
SYFT_GOLANG_SEARCH_LOCAL_MOD_CACHE_LICENSES - 在本地go module cache查找license信息
SYFT_GOLANG_SEARCH_REMOTE_LICENSES - 通过GOPROXY查找go module的license信息

如果你对license信息比较看重,我们可以基于上述环境变量配置再重新生成一次gin的SBOM:

$export SYFT_FORMAT_SPDX_JSON_PRETTY=true
$export SYFT_GOLANG_SEARCH_LOCAL_MOD_CACHE_LICENSES=true
$export SYFT_GOLANG_SEARCH_REMOTE_LICENSES=true
$syft . -o spdx-json=gin-sbom.spdx.json

关于 Java 和 JavaScript 项目

syft 同样能够为 Java (如 Maven, Gradle) 和 JavaScript (如 npm, yarn) 等项目生成 SPDX 或其他格式的 SBOM。其基本使用方式与 Go 项目类似,通常只需将扫描路径指向你的 Java 或 JavaScript 项目根目录即可。syft 会自动识别对应的包管理文件(如 pom.xml, package-lock.json)并解析依赖。更详细的用法和特定语言的注意事项,推荐查阅 anchore/syft 的官方文档。

让 SPDX SBOM 清单“说话”:将 Go 项目的 SPDX JSON 转换为 Excel

生成的 SPDX JSON 文件虽然结构清晰,便于机器处理,但对于需要提交给甲方或公司安全合规团队进行人工审计的场景,Excel 格式往往更受欢迎。

我们可以使用 Linux Foundation 维护的官方SPDX online Tools 来实现这个转换。

通过浏览器打开https://tools.spdx.org/app/convert/,选择将spdx json转换为xlsx格式,并上传gin-sbom.spdx.json文件,点击Convert:

转换成功后,下载生成的excel文件,该文件的内容如下截图:

转换后的 Excel 文档通常会包含多个工作表,例如:Document Information, Package Information, Per File Information (如果分析到文件级别), Relationships, Licensing Information 等。 通过这样的表格,团队成员可以更方便地进行许可证审计、版本检查和依赖关系梳理。

当然SPDX 社区和第三方也都提供了一些工具来帮助完成此类转换,有gui的,也有命令行,大家可以自己的需求使用不同的转换工具。

SBOM 的更广阔图景与 Go 开发者的行动

生成 SBOM 只是第一步。它的真正价值在于融入到整个软件开发生命周期中:

  • CI/CD 集成: 在构建过程中自动生成 SBOM,并进行漏洞扫描(例如与 Trivy、Grype 等工具结合)和许可证策略检查。
  • VEX (Vulnerability Exploitability eXchange): 结合 VEX 文档,可以更准确地判断 SBOM 中列出的漏洞在当前产品中是否真正可被利用,减少误报。
  • 持续监控: 定期重新生成 SBOM 并分析,以应对新出现的漏洞和组件更新。

对于我们 Gopher 而言,掌握 SBOM(特别是 SPDX 这样被广泛认可的标准)的生成和使用,不仅是满足日益增长的合规要求,更是提升自身软件质量、安全意识和专业素养的体现。Go 语言的静态编译特性和完善的模块系统 (go.mod),使得像 syft 这样的工具能够相对容易和准确地分析依赖关系,生成高质量的 SBOM。

小结

软件供应链安全是一项系统工程,而 SBOM 则是其中不可或缺的一块拼图。它为我们提供了一双“透视眼”,让我们能够清晰地了解软件的“前世今生”,从容应对潜在的风险。

无论是选择 SPDX、CycloneDX,还是 SWID 或 DSDX,理解并实践 SBOM 的核心理念至关重要。利用 syft 这样的工具,为你的 Go 项目(以及其他语言项目)生成并维护一份符合 SPDX 标准的 SBOM,都应该成为我们开发实践中的一项基本功。

现在,就动手为你的项目构建一份清晰的“软件家谱”吧!


聊一聊,也帮个忙:

  • 在你的工作中,是否已经开始被要求提供 SBOM?你主要关注 SBOM 的哪些方面(安全、合规、还是其他)?你通常使用哪种 SBOM 标准?
  • 除了 syft,你还知道或使用过哪些优秀的 SBOM 生成或分析工具?特别是针对 SPDX 格式的。
  • 你认为在 Go 社区,我们还可以做些什么来进一步推动 SBOM(尤其是 SPDX 标准)的普及和应用?

欢迎在评论区留下你的经验、思考和问题。如果你觉得这篇文章对你有帮助,也请转发给你身边的开发者朋友们,让更多人了解和重视 SBOM!

想与我进行更深入的 Go 语言、软件供应链安全与 AI 技术交流吗? 欢迎加入我的“Go & AI 精进营”知识星球

img{512x368}

我们星球见!


商务合作方式:撰稿、出书、培训、在线课程、合伙创业、咨询、广告合作。如有需求,请扫描下方公众号二维码,与我私信联系。

如发现本站页面被黑,比如:挂载广告、挖矿等恶意代码,请朋友们及时联系我。十分感谢! Go语言第一课 Go语言进阶课 Go语言精进之路1 Go语言精进之路2 Go语言编程指南
商务合作请联系bigwhite.cn AT aliyun.com

欢迎使用邮件订阅我的博客

输入邮箱订阅本站,只要有新文章发布,就会第一时间发送邮件通知你哦!

这里是 Tony Bai的个人Blog,欢迎访问、订阅和留言! 订阅Feed请点击上面图片

如果您觉得这里的文章对您有帮助,请扫描上方二维码进行捐赠 ,加油后的Tony Bai将会为您呈现更多精彩的文章,谢谢!

如果您希望通过微信捐赠,请用微信客户端扫描下方赞赏码:

如果您希望通过比特币或以太币捐赠,可以扫描下方二维码:

比特币:

以太币:

如果您喜欢通过微信浏览本站内容,可以扫描下方二维码,订阅本站官方微信订阅号“iamtonybai”;点击二维码,可直达本人官方微博主页^_^:
本站Powered by Digital Ocean VPS。
选择Digital Ocean VPS主机,即可获得10美元现金充值,可 免费使用两个月哟! 著名主机提供商Linode 10$优惠码:linode10,在 这里注册即可免费获 得。阿里云推荐码: 1WFZ0V立享9折!


View Tony Bai's profile on LinkedIn
DigitalOcean Referral Badge

文章

评论

  • 正在加载...

分类

标签

归档



View My Stats