-
-
Notifications
You must be signed in to change notification settings - Fork 4
Implement intermediate SBOM generation and SPDX serialization #63
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f48b525
c63949d
cd1690f
b53384a
24f6f4f
c0629bc
e8b391f
e0d980d
b5cfb1d
f525c31
49f09b5
9283211
8474fb6
d8c60c4
88628bc
c6fc9ef
7579e0b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
load("@rules_go//go:def.bzl", "go_binary", "go_library") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd put this into |
||
|
||
go_library( | ||
name = "spdx_lib", | ||
srcs = ["spdx.go"], | ||
importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/cmd/spdx", | ||
visibility = ["//visibility:private"], | ||
deps = [ | ||
"//:supplychain-go", | ||
"//internal/sbom", | ||
"@com_github_spdx_tools_golang//json", | ||
"@com_github_spdx_tools_golang//spdx", | ||
"@com_github_spdx_tools_golang//spdx/v2/common", | ||
"@com_github_spdx_tools_golang//tagvalue", | ||
"@com_github_spdx_tools_golang//yaml", | ||
], | ||
) | ||
|
||
go_binary( | ||
name = "spdx", | ||
embed = [":spdx_lib"], | ||
visibility = ["//visibility:public"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package main | ||
|
||
import ( | ||
"encoding/json" | ||
"flag" | ||
"fmt" | ||
"os" | ||
|
||
supplychain "github.com/bazel-contrib/supply-chain/lib/supplychain-go" | ||
"github.com/bazel-contrib/supply-chain/lib/supplychain-go/internal/sbom" | ||
spdxJson "github.com/spdx/tools-golang/json" | ||
"github.com/spdx/tools-golang/spdx" | ||
"github.com/spdx/tools-golang/spdx/v2/common" | ||
spdxTV "github.com/spdx/tools-golang/tagvalue" | ||
spdxYaml "github.com/spdx/tools-golang/yaml" | ||
) | ||
|
||
var ( | ||
outPath = flag.String("out", "", "The path to write the generated SPDX SBOM.") | ||
configPath = flag.String("config", "", "The path to the SBOM generation configuration file.") | ||
format = flag.String("format", "", "The output format of the SPDX SBOM.") | ||
) | ||
|
||
func main() { | ||
flag.Parse() | ||
var config sbom.GenConfig | ||
|
||
configBytes, err := os.ReadFile(*configPath) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
json.Unmarshal(configBytes, &config) | ||
|
||
out, err := os.OpenFile(*outPath, os.O_WRONLY|os.O_CREATE, 0664) | ||
if err != nil { | ||
panic(err) | ||
} | ||
defer out.Close() | ||
|
||
doc, err := GenerateDocument(config) | ||
if err != nil { | ||
panic(err) | ||
} | ||
|
||
must := func(err error) { | ||
if err != nil { | ||
panic(err) | ||
} | ||
} | ||
|
||
switch *format { | ||
case "json": | ||
must(spdxJson.Write(doc, out)) | ||
case "yaml": | ||
must(spdxYaml.Write(doc, out)) | ||
case "tag-value": | ||
must(spdxTV.Write(doc, out)) | ||
default: | ||
panic(fmt.Sprintf("'%s' is not a supported format", *format)) | ||
} | ||
} | ||
|
||
func GenerateDocument(config sbom.GenConfig) (*spdx.Document, error) { | ||
spdxPackages := make([]*spdx.Package, len(config.Deps)) | ||
|
||
for i, dep := range config.Deps { | ||
pkgMetadata, err := supplychain.ReadPackageMetadataFromFile(dep.Metadata) | ||
if err != nil { | ||
return nil, err | ||
} | ||
spdxPackages[i] = &spdx.Package{ | ||
PackageSPDXIdentifier: common.ElementID(fmt.Sprintf("dep-%d", i)), | ||
PackageExternalReferences: []*spdx.PackageExternalReference{ | ||
{ | ||
Category: "PACKAGE-MANAGER", | ||
RefType: "purl", | ||
Locator: pkgMetadata.GetPURL().String(), | ||
}, | ||
}, | ||
PackageName: pkgMetadata.GetPURL().Name, | ||
} | ||
} | ||
|
||
doc := spdx.Document{ | ||
SPDXIdentifier: "DOCUMENT", | ||
SPDXVersion: "SPDX-2.3", | ||
Packages: spdxPackages, | ||
CreationInfo: &spdx.CreationInfo{}, | ||
} | ||
|
||
return &doc, nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,16 +1,32 @@ | ||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= | ||
github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= | ||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= | ||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= | ||
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= | ||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= | ||
github.com/package-url/packageurl-go v0.1.3 h1:4juMED3hHiz0set3Vq3KeQ75KD1avthoXLtmE3I0PLs= | ||
github.com/package-url/packageurl-go v0.1.3/go.mod h1:nKAWB8E6uk1MHqiS/lQb9pYBGH2+mdJ2PJc2s50dQY0= | ||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= | ||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= | ||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
github.com/stretchr/testify v1.11.0 h1:ib4sjIrwZKxE5u/Japgo/7SJV3PvgjGiRNAvTVGqQl8= | ||
github.com/stretchr/testify v1.11.0/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= | ||
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM= | ||
github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= | ||
github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= | ||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= | ||
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= | ||
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= | ||
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= | ||
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= | ||
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= | ||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= | ||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= | ||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= | ||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= | ||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= | ||
sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= | ||
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
load("@rules_go//go:def.bzl", "go_library") | ||
|
||
go_library( | ||
name = "sbom", | ||
srcs = ["genconfig.go"], | ||
importpath = "github.com/bazel-contrib/supply-chain/lib/supplychain-go/internal/sbom", | ||
visibility = ["//:__subpackages__"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package sbom | ||
|
||
type GenConfig struct { | ||
Deps []DepConfig `json:"deps"` | ||
} | ||
|
||
type DepConfig struct { | ||
Metadata string `json:"metadata"` | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
"""Rules for generating a SBOM out of a target.""" | ||
|
||
load("@bazel_skylib//:bzl_library.bzl", "bzl_library") | ||
|
||
package( | ||
default_package_metadata = ["//:package_metadata"], | ||
default_visibility = ["//visibility:public"], | ||
) | ||
|
||
filegroup( | ||
name = "srcs", | ||
srcs = glob(["**"]), | ||
) | ||
|
||
bzl_library( | ||
name = "sbom", | ||
srcs = [ | ||
":sbom.bzl", | ||
"//gather_metadata", | ||
"//:core_metadata", | ||
], | ||
visibility = ["//visibility:public"], | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
"""Providers for the sbom rules. | ||
|
||
Warning: This is private to the aspect that walks the tree. The API is subject | ||
to change at any release. | ||
""" | ||
|
||
SbomInfo = provider( | ||
doc = "A provider that contains the configuration for generating an SBOM.", | ||
fields = { | ||
"config": "The configuration file for generating the SBOM.", | ||
} | ||
) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
load("providers.bzl", "SbomInfo") | ||
load( | ||
"@package_metadata//:defs.bzl", | ||
"PackageAttributeInfo", | ||
"PackageMetadataInfo", | ||
) | ||
load( | ||
"@package_metadata//licenses/providers:license_kind_info.bzl", | ||
"LicenseKindInfo", | ||
) | ||
load( | ||
"@supply_chain_tools//gather_metadata:core.bzl", | ||
"gather_metadata_info_common", | ||
"should_traverse", | ||
) | ||
load( | ||
"@supply_chain_tools//gather_metadata:providers.bzl", | ||
"null_transitive_metadata_info", | ||
"TransitiveMetadataInfo", | ||
) | ||
|
||
def _gather_metadata_info_impl(target, ctx): | ||
return gather_metadata_info_common( | ||
target, | ||
ctx, | ||
want_providers = [PackageAttributeInfo, PackageMetadataInfo, LicenseKindInfo], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do we need |
||
provider_factory = TransitiveMetadataInfo, | ||
null_provider_instance = null_transitive_metadata_info, | ||
filter_func = should_traverse, | ||
) | ||
|
||
gather_metadata_info = aspect( | ||
doc = """Collects metadata providers into a single TransitiveMetadataInfo provider.""", | ||
implementation = _gather_metadata_info_impl, | ||
attr_aspects = ["*"], | ||
attrs = { | ||
"_trace": attr.label(default = "@supply_chain_tools//gather_metadata:trace_target"), | ||
}, | ||
provides = [TransitiveMetadataInfo], | ||
apply_to_generating_rules = True, | ||
) | ||
|
||
def _sbom_impl(ctx): | ||
transitive_metadata_info = ctx.attr.target[TransitiveMetadataInfo] | ||
transitive_inputs = [] | ||
config = { "deps": [] } | ||
for m in transitive_metadata_info.metadata.to_list(): | ||
config["deps"].append({ | ||
"metadata": m.metadata.path | ||
}) | ||
transitive_inputs.append(m.files) | ||
|
||
sbom_gen_config = ctx.actions.declare_file("{name}.sbom.config.json".format(name=ctx.attr.name)) | ||
ctx.actions.write(sbom_gen_config, json.encode(config)) | ||
|
||
return [ | ||
DefaultInfo(files=depset( | ||
[sbom_gen_config], | ||
transitive=transitive_inputs | ||
)), | ||
SbomInfo(config=sbom_gen_config), | ||
] | ||
|
||
def sbom_rule(gathering_aspect): | ||
return rule( | ||
_sbom_impl, | ||
attrs = { | ||
"target": attr.label(aspects = [gathering_aspect], doc="The target for which to generate an SBOM."), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. From internal experience, this should be |
||
}, | ||
) | ||
|
||
sbom = sbom_rule(gathering_aspect=gather_metadata_info) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mind splitting this one out into its own PR?
Also, we should probably add an alias for the old name