Skip to content
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

Merged
merged 9 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .github/workflows/sbom.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,18 +84,18 @@ jobs:

- name: Install SPDX Tools
run: |
wget https://github.com/spdx/tools/releases/download/v2.2.7/spdx-tools-2.2.7.zip
unzip spdx-tools-2.2.7.zip
wget https://github.com/spdx/tools-java/releases/download/v1.0.4/tools-java-1.0.4.zip
unzip tools-java-1.0.4.zip

- name: Generate and Validate
run: |
img=$(go run ./ build ./)
go run ./ deps $img --sbom=spdx > sbom.txt
go run ./ deps $img --sbom=spdx | tee sbom.json

java -jar ./spdx-tools-2.2.7-jar-with-dependencies.jar Verify sbom.txt
java -jar ./tools-java-1.0.4-jar-with-dependencies.jar Verify sbom.json

- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: sbom.txt
path: sbom.txt
name: sbom.json
path: sbom.json
249 changes: 175 additions & 74 deletions internal/sbom/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand All @@ -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,
Copy link
Member

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? 🤔

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit since it's internal anyway but these types can be unexported

Copy link
Collaborator Author

Choose a reason for hiding this comment

The 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 internal/ so I'm sort of inclined to leave it. WDYT?

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"`
}
2 changes: 1 addition & 1 deletion pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ func spdx(version string) sbomber {
if err != nil {
return nil, "", err
}
return b, ctypes.SPDXMediaType, nil
return b, ctypes.SPDXJSONMediaType, nil
}
}

Expand Down