-
Notifications
You must be signed in to change notification settings - Fork 85
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
spdx: initial sbom framwork + spdx encoder #1468
base: main
Are you sure you want to change the base?
Conversation
Should be moved out of the Rambling on whyThe |
0e7bc81
to
ae67e46
Compare
11f31ac
to
be73a74
Compare
If anyone has a suggestion on testing something that bakes in
|
You can put together a |
3abf86b
to
aa194f1
Compare
I'll start using fixup commits from this point on to help reviewers until I get approvals, then I'll rebase all the fixup commits. |
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.
LGTM
sbom/spdx/encoder_test.go
Outdated
// TODO(DO NO MERGE): This feels terrible | ||
ignoreCreatedTimestamp := cmp.FilterPath(func(p cmp.Path) bool { | ||
sf, ok := p.Index(3).(cmp.MapIndex) | ||
return ok && sf.String() == `["created"]` | ||
}, cmp.Ignore()) |
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.
@hdonnay is this what you were thinking? Is there a cleaner way to do it?
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.
If you're leaving them as map[string]any
, you could use cmpopts.IgnoreMapEntries
. If you turn them back into the strong types, you could do:
cmpopts.AcyclicTransformer("IgnoreTimestamps", func(info *v2_3.CreationInfo) *v2_3.CreationInfo{
info.Created = "2006-01-02T15:04:05Z"
return info
})
ac99a3b
to
40685d2
Compare
Signed-off-by: Brad Lugo <[email protected]>
29c8071
to
f8bb863
Compare
Adding a function to be able to convert index reports into SPDX documents and SPDX documents into index reports. Signed-off-by: crozzy <[email protected]> Signed-off-by: Brad Lugo <[email protected]>
f8bb863
to
795e5f2
Compare
sbom/spdx/encoder.go
Outdated
type Version string | ||
|
||
const ( | ||
V2_3 Version = "v2.3" | ||
) | ||
|
||
type Format string | ||
|
||
const JSON Format = "json" | ||
|
||
type Creator struct { | ||
Creator string | ||
// In accordance to the SPDX v2 spec, CreatorType should be one of "Person", "Organization", or "Tool" | ||
CreatorType string | ||
} |
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.
These types need documentation if they're exported.
sbom/spdx/encoder.go
Outdated
type Encoder struct { | ||
Version Version | ||
Format Format | ||
Creators []Creator | ||
DocumentName string | ||
DocumentNamespace string | ||
DocumentComment string | ||
} |
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.
A user is expected to construct this themselves? Is the zero value "ready" to use?
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.
I've gone back and forth on this. Many of the zero values aren't to spec, but disallowing empty strings felt weird. One idea I toyed around with was having a NewDefaultEncoder()
that created an Encoder
with some claircore values, e.g., Creator[0].CreatorType = "Tool", Creator[0].Creator = "claircore"
. What do you think? (cc @RTann)
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.
@dcaravel, can I get your opinion on this as well?
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.
Version |
I'd be OK with a default, would need to be configurable by the client in the future as multiple formats/version are supported. |
Format |
same ^^ |
Creators |
be OK adding claircore as a default Tool but must have the option to add other tools, people, organizations, etc. that claircore will not know about. |
DocumentName |
current state I see the client having to provide this - not sure how a default value could be derived - an index report does not contain a public friendly identifier that would be meaningful to a client/users (at least in the ACS case), but it's also contextual: for an image this could be the image digest, or the full image ref - for a node this could be the node name or IP, etc. |
DocumentComment |
same ^, this may be the only field available for 'extra context' from a client, i'd be OK with a generic default value, however the client should be able to override the value - as an example, for ACS we're planning on adding a note to indicate this is tech preview. |
DocumentNamespace |
same ^, leaning toward making this clients choice as well, the spec says this should be an "unambiguous mechanism for other SPDX documents to reference SPDX elements within this SPDX document", which the index report does not have that context, and that unique addressability may not apply to type 'Analyzed' SBOMs either. The example from the spec: https://[CreatorWebsite]/[pathToSpdx]/[DocumentName]-[UUID] hints this may be associated with some external storage/path/etc. which also is not something claircore knows about or is in index report. Am therefore leaning toward putting this on the client to specify. |
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.
Just pushed an update for this. @dcaravel, what'd you think?
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.
Nice, left a review below.
super nit and possibly a personal preference: Setting the doc name, namespace, comment on the Encoder object seemed easier to read vs. strings passed to NewDefaultEncoder(string, string, string)
(I had put them in the wrong order initially). Perhaps a good candidate for functional options? - but that may be over complicating it.
sbom/spdx/encoder_test.go
Outdated
// TODO(DO NO MERGE): This feels terrible | ||
ignoreCreatedTimestamp := cmp.FilterPath(func(p cmp.Path) bool { | ||
sf, ok := p.Index(3).(cmp.MapIndex) | ||
return ok && sf.String() == `["created"]` | ||
}, cmp.Ignore()) |
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.
If you're leaving them as map[string]any
, you could use cmpopts.IgnoreMapEntries
. If you turn them back into the strong types, you could do:
cmpopts.AcyclicTransformer("IgnoreTimestamps", func(info *v2_3.CreationInfo) *v2_3.CreationInfo{
info.Created = "2006-01-02T15:04:05Z"
return info
})
Apply suggested changes locally
Accept a Writer instead of returning a Reader
Fix godoc comments
Add NewDefaultEncoder()
Fix Creator fields ordering in NewDefaultEncoder()
sbom/sbom.go
Outdated
"io" | ||
) | ||
|
||
// Encoder is an interface to convert a claircore.IndexReport into an io.Reader |
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.
// Encoder is an interface to convert a claircore.IndexReport into an io.Reader | |
// Encoder is an interface to write a Software Bill of Materials (SBOM) representation of a [claircore.IndexReport] to an output stream. |
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.
Don't forget about this one ^ :)
Address Ross's review
goimports sbom/sbom.go
Change the NewDefaultEncoder() to use an option pattern
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.
LGTM, tested on a few images (namely nginx:1.24.0
and quay.io/rhacs-eng/main:4.6.2
) comparing the v4.IndexReport
from stackrox to the produced SBOMs, and the output was as expected / explainable.
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.
Approving to ensure this can move along. No true blockers from me, though perhaps the import ordering may be one? I also ask for consideration of the strconv.Atoi
comments (do we need these calls?) and the comment about break
ing out of the loop
sbom/sbom.go
Outdated
"io" | ||
) | ||
|
||
// Encoder is an interface to convert a claircore.IndexReport into an io.Reader |
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.
Don't forget about this one ^ :)
"github.com/spdx/tools-golang/spdx/v2/v2_3" | ||
) | ||
|
||
type Version string |
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.
won't block the PR, but I think each public field usually has a godoc in this repo
sbom/spdx/encoder.go
Outdated
return nil, ctx.Err() | ||
} | ||
|
||
rPkgId, err := strconv.Atoi(r.Package.ID) |
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.
won't block the PR over this, but nit: pkgID
or rPkgID
(what does the r
stand for?)
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.
what does the r stand for?
"record", as in "the current claircore record we're working with." I think I was creating the spdx package ID and saving that to pkgId
in some other commit.
sbom/spdx/encoder.go
Outdated
pkgIds = append(pkgIds, rPkgId) | ||
|
||
if r.Package.Source != nil && r.Package.Source.Name != "" { | ||
rSrcPkgId, err := strconv.Atoi(r.Package.Source.ID) |
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.
same nit here, but optional
sbom/spdx/encoder.go
Outdated
|
||
// Record Distributions for this package. | ||
if r.Distribution != nil { | ||
rDistId, err := strconv.Atoi(r.Distribution.ID) |
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.
just realized, do we use the IDs for anything other than converting them into int
s? If not, then can we just keep them as strings and make the map key string instead of int? Same idea for the slices. Not sure if we have to keep doing this conversion
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.
We convert them to int
s to sort them. The return from IndexReport.IndexRecords()
is non-deterministic because it iterates over IndexReport.Packages
, which is a map. In order to have a deterministic output, both to test against and for users to be able to run a diff between reports, we need to have some sort of ordering structuring. This is what I came up with, but I'm happy to revisit how we do it (perhaps in a follow up PR 🙂)
Some of this context is captured in a comment here: Some context here: https://github.com/quay/claircore/pull/1468/files#diff-9730b2b5baa2c38c549f0b3c1077fddf654896f316b79c2c8f3f77d5fb7ccd1cR273-R276
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.
so if we just cared about sorting for determinism, then I think we can keep it as strings. If we also want to have the IDs in increasing order, then converting to int makes sense. Feel free to change in a followup if you decide to change it
sbom/spdx/encoder.go
Outdated
return nil, err | ||
} | ||
|
||
srcPkg, ok := pkgs[rSrcPkgId] |
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.
won't block the PR over this, but wondering why we add the source here instead of just waiting until we see it? Do we not always see it? In the case we add it here and then see it again later, do we risk potentially duplicating data?
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.
@crozzy will likely be able to explain this better, but my understanding is that source packages aren't always recorded as root-level packages. In fact, I think rhcc source packages are the only ones that are also recorded as root-level packages.
For clarity, when I say "root-level packages" here, I'm referring to IndexReport.Packages
as opposed to IndexReport.Packages.Source
.
don't forget to sign your commits :) Also looks like there are still a bunch. Seems like you are on top of that, though, with the |
Yup, thank you! It's a new thing I'm trying. The fixup commits have a message body detailing more about the commit and giving reviewers an easier history to work with. Then, when I get approvals from all participating reviewers, I |
Address Ross's latest review
sbom/sbom.go
Outdated
// Encoder is an interface to convert a claircore.IndexReport and writes it to | ||
// w. | ||
// that contains a Software Bill of Materials representation. |
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.
nit: I think this is a mix of the old version and the new version of this comment?
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.
🤦 I didn't mean to add this to that commit. Should be fixed in the latest one + all the other godoc changes.
Add godoc comments to exported values
Description
These changes introduce the initial SBOM framework into claircore, generating an SPDX 2.3 JSON document from a
claircore.IndexReport
.Design
Design doc: https://docs.google.com/document/d/1dRKMWjmkpYO5oN_Y-S_0R2vwyvcdXHCNejM3C-F2DIc/edit?tab=t.0