-
Notifications
You must be signed in to change notification settings - Fork 406
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
Convert our SPDX SBOMs to spdx+json. #740
Changes from all commits
14e0fa9
229986c
fa93b79
9db088d
752de77
2cacf57
7b38c6e
00bd3ac
111f9c3
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 |
---|---|---|
|
@@ -16,11 +16,9 @@ package sbom | |
|
||
import ( | ||
"bytes" | ||
"encoding/base64" | ||
"encoding/hex" | ||
"encoding/json" | ||
"fmt" | ||
"strings" | ||
"text/template" | ||
"time" | ||
|
||
v1 "github.com/google/go-containerregistry/pkg/v1" | ||
|
@@ -40,82 +38,185 @@ func GenerateSPDX(koVersion string, date time.Time, mod []byte, imgDigest v1.Has | |
return nil, err | ||
} | ||
|
||
mainPackageID := "SPDXRef-Package-" + strings.ReplaceAll(bi.Main.Path, "/", ".") | ||
|
||
doc := Document{ | ||
Version: Version, | ||
DataLicense: "CC0-1.0", | ||
ID: "SPDXRef-DOCUMENT", | ||
Name: bi.Main.Path, | ||
Namespace: "http://spdx.org/spdxdocs/" + bi.Main.Path, | ||
DocumentDescribes: []string{mainPackageID}, | ||
CreationInfo: CreationInfo{ | ||
Created: date.Format(dateFormat), | ||
Creators: []string{"Tool: ko " + koVersion}, | ||
}, | ||
Packages: make([]Package, 0, 1+len(bi.Deps)), | ||
Relationships: make([]Relationship, 0, 1+len(bi.Deps)), | ||
} | ||
|
||
doc.Relationships = append(doc.Relationships, Relationship{ | ||
Element: "SPDXRef-DOCUMENT", | ||
Type: "DESCRIBES", | ||
Related: mainPackageID, | ||
}) | ||
|
||
doc.Packages = append(doc.Packages, Package{ | ||
Name: bi.Main.Path, | ||
ID: mainPackageID, | ||
// TODO: PackageSupplier: "Organization: " + bs.Main.Path | ||
DownloadLocation: "https://" + bi.Main.Path, | ||
FilesAnalyzed: false, | ||
// TODO: PackageHomePage: "https://" + bi.Main.Path, | ||
LicenseConcluded: NOASSERTION, | ||
LicenseDeclared: NOASSERTION, | ||
CopyrightText: NOASSERTION, | ||
ExternalRefs: []ExternalRef{{ | ||
Category: "PACKAGE_MANAGER", | ||
Type: "purl", | ||
Locator: ociRef(bi.Path, imgDigest), | ||
}}, | ||
}) | ||
|
||
for _, dep := range bi.Deps { | ||
depID := fmt.Sprintf("SPDXRef-Package-%s-%s", | ||
strings.ReplaceAll(dep.Path, "/", "."), | ||
dep.Version) | ||
|
||
doc.Relationships = append(doc.Relationships, Relationship{ | ||
Element: mainPackageID, | ||
Type: "DEPENDS_ON", | ||
Related: depID, | ||
}) | ||
|
||
pkg := Package{ | ||
Name: dep.Path, | ||
ID: depID, | ||
Version: dep.Version, | ||
// TODO: PackageSupplier: "Organization: " + dep.Path | ||
DownloadLocation: fmt.Sprintf("https://proxy.golang.org/%s/@v/%s.zip", dep.Path, dep.Version), | ||
FilesAnalyzed: false, | ||
LicenseConcluded: NOASSERTION, | ||
LicenseDeclared: NOASSERTION, | ||
CopyrightText: NOASSERTION, | ||
ExternalRefs: []ExternalRef{{ | ||
Category: "PACKAGE_MANAGER", | ||
Type: "purl", | ||
Locator: goRef(dep.Path, dep.Version), | ||
}}, | ||
} | ||
|
||
if dep.Sum != "" { | ||
pkg.Checksums = []Checksum{{ | ||
Algorithm: "SHA256", | ||
Value: h1ToSHA256(dep.Sum), | ||
}} | ||
} | ||
|
||
doc.Packages = append(doc.Packages, pkg) | ||
} | ||
|
||
var buf bytes.Buffer | ||
if err := tmpl.Execute(&buf, tmplInfo{ | ||
BuildInfo: *bi, | ||
Date: date.Format(dateFormat), | ||
KoVersion: koVersion, | ||
ImgDigest: imgDigest, | ||
}); err != nil { | ||
enc := json.NewEncoder(&buf) | ||
enc.SetIndent("", " ") | ||
if err := enc.Encode(doc); err != nil { | ||
return nil, err | ||
} | ||
return buf.Bytes(), nil | ||
} | ||
|
||
type tmplInfo struct { | ||
BuildInfo | ||
Date, UUID, KoVersion string | ||
ImgDigest v1.Hash | ||
// Below this is forked from here: | ||
// https://github.com/kubernetes-sigs/bom/blob/main/pkg/spdx/json/v2.2.2/types.go | ||
|
||
/* | ||
Copyright 2022 The Kubernetes Authors. | ||
|
||
Licensed under the Apache License, Version 2.0 (the "License"); | ||
you may not use this file except in compliance with the License. | ||
You may obtain a copy of the License at | ||
|
||
http://www.apache.org/licenses/LICENSE-2.0 | ||
|
||
Unless required by applicable law or agreed to in writing, software | ||
distributed under the License is distributed on an "AS IS" BASIS, | ||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
See the License for the specific language governing permissions and | ||
limitations under the License. | ||
*/ | ||
|
||
const ( | ||
NOASSERTION = "NOASSERTION" | ||
mattmoor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
Version = "SPDX-2.2" | ||
) | ||
|
||
type Document struct { | ||
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. Nit since it's internal anyway but these types can be unexported 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 was trying to avoid touching this code too much. I was about to change, but remembered this is in |
||
ID string `json:"SPDXID"` | ||
Name string `json:"name"` | ||
Version string `json:"spdxVersion"` | ||
CreationInfo CreationInfo `json:"creationInfo"` | ||
DataLicense string `json:"dataLicense"` | ||
Namespace string `json:"documentNamespace"` | ||
DocumentDescribes []string `json:"documentDescribes,omitempty"` | ||
Files []File `json:"files,omitempty"` | ||
Packages []Package `json:"packages,omitempty"` | ||
Relationships []Relationship `json:"relationships,omitempty"` | ||
} | ||
|
||
type CreationInfo struct { | ||
Created string `json:"created"` // Date | ||
Creators []string `json:"creators,omitempty"` | ||
LicenseListVersion string `json:"licenseListVersion,omitempty"` | ||
} | ||
|
||
// TODO: use k8s.io/release/pkg/bom | ||
var tmpl = template.Must(template.New("").Funcs(template.FuncMap{ | ||
"dots": func(s string) string { return strings.ReplaceAll(s, "/", ".") }, | ||
"goRef": func(p, v string) string { return goRef(p, v) }, | ||
"ociRef": func(p string, d v1.Hash) string { return ociRef(p, d) }, | ||
"h1toSHA256": func(s string) (string, error) { | ||
if !strings.HasPrefix(s, "h1:") { | ||
return "", fmt.Errorf("malformed sum prefix: %q", s) | ||
} | ||
b, err := base64.StdEncoding.DecodeString(s[3:]) | ||
if err != nil { | ||
return "", fmt.Errorf("malformed sum: %q: %w", s, err) | ||
} | ||
return hex.EncodeToString(b), nil | ||
}, | ||
}).Parse(`SPDXVersion: SPDX-2.2 | ||
DataLicense: CC0-1.0 | ||
SPDXID: SPDXRef-DOCUMENT | ||
DocumentName: {{ .BuildInfo.Main.Path }} | ||
DocumentNamespace: http://spdx.org/spdxpackages/{{ .BuildInfo.Main.Path }} | ||
Creator: Tool: ko {{ .KoVersion }} | ||
Created: {{ .Date }} | ||
|
||
##### Package representing {{ .BuildInfo.Main.Path }} | ||
|
||
PackageName: {{ .BuildInfo.Main.Path }} | ||
SPDXID: SPDXRef-Package-{{ .BuildInfo.Main.Path | dots }} | ||
PackageSupplier: Organization: {{ .BuildInfo.Main.Path }} | ||
PackageDownloadLocation: https://{{ .BuildInfo.Main.Path }} | ||
FilesAnalyzed: false | ||
PackageHomePage: https://{{ .BuildInfo.Main.Path }} | ||
PackageLicenseConcluded: NOASSERTION | ||
PackageLicenseDeclared: NOASSERTION | ||
PackageCopyrightText: NOASSERTION | ||
PackageLicenseComments: NOASSERTION | ||
PackageComment: NOASSERTION | ||
ExternalRef: PACKAGE-MANAGER purl {{ ociRef .Path .ImgDigest }} | ||
|
||
Relationship: SPDXRef-DOCUMENT DESCRIBES SPDXRef-Package-{{ .BuildInfo.Main.Path | dots }} | ||
|
||
{{ range .Deps }} | ||
Relationship: SPDXRef-Package-{{ $.Main.Path | dots }} DEPENDS_ON SPDXRef-Package-{{ .Path | dots }}-{{ .Version }} | ||
|
||
##### Package representing {{ .Path }} | ||
|
||
PackageName: {{ .Path }} | ||
SPDXID: SPDXRef-Package-{{ .Path | dots }}-{{ .Version }} | ||
PackageVersion: {{ .Version }} | ||
PackageSupplier: Organization: {{ .Path }} | ||
PackageDownloadLocation: https://proxy.golang.org/{{ .Path }}/@v/{{ .Version }}.zip | ||
FilesAnalyzed: false | ||
{{ if .Sum }}PackageChecksum: SHA256: {{ .Sum | h1toSHA256 }} | ||
{{ end }}PackageLicenseConcluded: NOASSERTION | ||
PackageLicenseDeclared: NOASSERTION | ||
PackageCopyrightText: NOASSERTION | ||
PackageLicenseComments: NOASSERTION | ||
PackageComment: NOASSERTION | ||
ExternalRef: PACKAGE-MANAGER purl {{ goRef .Path .Version }} | ||
|
||
{{ end }} | ||
`)) | ||
type Package struct { | ||
ID string `json:"SPDXID"` | ||
Name string `json:"name"` | ||
Version string `json:"versionInfo"` | ||
FilesAnalyzed bool `json:"filesAnalyzed"` | ||
LicenseDeclared string `json:"licenseDeclared"` | ||
LicenseConcluded string `json:"licenseConcluded"` | ||
Description string `json:"description,omitempty"` | ||
DownloadLocation string `json:"downloadLocation"` | ||
Originator string `json:"originator,omitempty"` | ||
SourceInfo string `json:"sourceInfo,omitempty"` | ||
CopyrightText string `json:"copyrightText"` | ||
HasFiles []string `json:"hasFiles,omitempty"` | ||
LicenseInfoFromFiles []string `json:"licenseInfoFromFiles,omitempty"` | ||
Checksums []Checksum `json:"checksums,omitempty"` | ||
ExternalRefs []ExternalRef `json:"externalRefs,omitempty"` | ||
VerificationCode *PackageVerificationCode `json:"packageVerificationCode,omitempty"` | ||
} | ||
|
||
type PackageVerificationCode struct { | ||
Value string `json:"packageVerificationCodeValue"` | ||
ExcludedFiles []string `json:"packageVerificationCodeExcludedFiles,omitempty"` | ||
} | ||
|
||
type File struct { | ||
ID string `json:"SPDXID"` | ||
Name string `json:"fileName"` | ||
CopyrightText string `json:"copyrightText"` | ||
NoticeText string `json:"noticeText,omitempty"` | ||
LicenseConcluded string `json:"licenseConcluded"` | ||
Description string `json:"description,omitempty"` | ||
FileTypes []string `json:"fileTypes,omitempty"` | ||
LicenseInfoInFile []string `json:"licenseInfoInFiles"` // List of licenses | ||
Checksums []Checksum `json:"checksums"` | ||
} | ||
|
||
type Checksum struct { | ||
Algorithm string `json:"algorithm"` | ||
Value string `json:"checksumValue"` | ||
} | ||
|
||
type ExternalRef struct { | ||
Category string `json:"referenceCategory"` | ||
Locator string `json:"referenceLocator"` | ||
Type string `json:"referenceType"` | ||
} | ||
|
||
type Relationship struct { | ||
Element string `json:"spdxElementId"` | ||
Type string `json:"relationshipType"` | ||
Related string `json:"relatedSpdxElement"` | ||
} |
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.
This isn't very likely to work. Can we make this a proxy.golang.org URL? 🤔
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 used this before 🤔
https://github.com/google/ko/blob/a61e14dc6a45d90e90ae0bec7bf3d51e48e9b5aa/internal/sbom/spdx.go#L89
I can TAL later, my tether time is up :)