Skip to content

Commit

Permalink
Convert our SPDX SBOMs to spdx+json. (ko-build#740)
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmoor authored Jul 1, 2022
1 parent a61e14d commit 12e5001
Show file tree
Hide file tree
Showing 3 changed files with 182 additions and 81 deletions.
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,
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"
Version = "SPDX-2.2"
)

type Document struct {
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

0 comments on commit 12e5001

Please sign in to comment.