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

Start emitting multi-arch SBOMs for SPDX with ko #743

Merged
merged 8 commits into from
Jul 5, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 49 additions & 8 deletions .github/workflows/sbom.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,14 +55,14 @@ jobs:
- name: Generate and Validate
run: |
img=$(go run ./ build ./)
go run ./ deps $img --sbom=cyclonedx > sbom.json
./cyclonedx-linux-x64 validate --input-file=sbom.json --fail-on-errors
go run ./ deps $img --sbom=cyclonedx > cyclonedx.json
./cyclonedx-linux-x64 validate --input-file=cyclonedx.json --fail-on-errors

- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: sbom.json
path: sbom.json
name: cyclonedx.json
path: cyclonedx.json

spdx:
name: Validate SPDX SBOM
Expand Down Expand Up @@ -90,12 +90,53 @@ jobs:
- name: Generate and Validate
run: |
img=$(go run ./ build ./)
go run ./ deps $img --sbom=spdx | tee sbom.json
go run ./ deps $img --sbom=spdx | tee spdx.json

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

- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: sbom.json
path: sbom.json
name: spdx.json
path: spdx.json

spdx-multi-arch:
name: Validate SPDX multi-arch SBOM
runs-on: ubuntu-latest

env:
KO_DOCKER_REPO: localhost:1338

steps:
- uses: actions/setup-go@v3
with:
go-version: 1.17
check-latest: true
- name: Install cmd/registry
run: |
go install github.com/google/go-containerregistry/cmd/registry@latest
registry &
- uses: actions/checkout@v3

- name: Install SPDX Tools
run: |
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: Install Cosign
uses: sigstore/cosign-installer@v2.4.0
with:
cosign-release: 'v1.7.2'

- name: Generate and Validate
run: |
img=$(go run ./ build --platform=linux/amd64,linux/arm64 ./)
cosign download sbom $img | tee spdx-multi-arch.json

java -jar ./tools-java-1.0.4-jar-with-dependencies.jar Verify spdx-multi-arch.json

- uses: actions/upload-artifact@v3
if: ${{ always() }}
with:
name: spdx-multi-arch.json
path: spdx-multi-arch.json
51 changes: 15 additions & 36 deletions internal/sbom/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,36 +19,11 @@ import (
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"strings"

v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/sigstore/cosign/pkg/oci"
)

func bomRef(path, version string) string {
return fmt.Sprintf("pkg:golang/%s@%s?type=module", path, version)
}

func goRef(path, version string) string {
// Try to lowercase the first 2 path elements to comply with spec
// https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang
p := strings.Split(path, "/")
if len(p) > 2 {
path = strings.Join(
append(
[]string{strings.ToLower(p[0]), strings.ToLower(p[1])},
p[2:]...,
), "/",
)
}
return fmt.Sprintf("pkg:golang/%s@%s?type=module", path, version)
}

func ociRef(path string, imgDigest v1.Hash) string {
parts := strings.Split(path, "/")
return fmt.Sprintf("pkg:oci/%s@%s", parts[len(parts)-1], imgDigest.String())
}

func h1ToSHA256(s string) string {
if !strings.HasPrefix(s, "h1:") {
return ""
Expand All @@ -60,7 +35,7 @@ func h1ToSHA256(s string) string {
return hex.EncodeToString(b)
}

func GenerateCycloneDX(mod []byte) ([]byte, error) {
func GenerateImageCycloneDX(mod []byte) ([]byte, error) {
var err error
mod, err = massageGoVersionM(mod)
if err != nil {
Expand All @@ -78,11 +53,11 @@ func GenerateCycloneDX(mod []byte) ([]byte, error) {
Version: 1,
Metadata: metadata{
Component: component{
BOMRef: bomRef(bi.Main.Path, bi.Main.Version),
BOMRef: bomRef(&bi.Main),
Type: "application",
Name: bi.Main.Path,
Version: bi.Main.Version,
Purl: bomRef(bi.Main.Path, bi.Main.Version),
Purl: bomRef(&bi.Main),
ExternalReferences: []externalReference{{
URL: "https://" + bi.Main.Path,
Type: "vcs",
Expand All @@ -97,12 +72,12 @@ func GenerateCycloneDX(mod []byte) ([]byte, error) {
// TODO: include bi.Settings?
},
Dependencies: []dependency{{
Ref: bomRef(bi.Main.Path, bi.Main.Version),
Ref: bomRef(&bi.Main),
}},
Compositions: []composition{{
Aggregate: "complete",
Dependencies: []string{
bomRef(bi.Main.Path, bi.Main.Version),
bomRef(&bi.Main),
},
}, {
Aggregate: "unknown",
Expand All @@ -115,12 +90,12 @@ func GenerateCycloneDX(mod []byte) ([]byte, error) {
continue
}
comp := component{
BOMRef: bomRef(dep.Path, dep.Version),
BOMRef: bomRef(dep),
Type: "library",
Name: dep.Path,
Version: dep.Version,
Scope: "required",
Purl: bomRef(dep.Path, dep.Version),
Purl: bomRef(dep),
ExternalReferences: []externalReference{{
URL: "https://" + dep.Path,
Type: "vcs",
Expand All @@ -133,12 +108,12 @@ func GenerateCycloneDX(mod []byte) ([]byte, error) {
}}
}
doc.Components = append(doc.Components, comp)
doc.Dependencies[0].DependsOn = append(doc.Dependencies[0].DependsOn, bomRef(dep.Path, dep.Version))
doc.Dependencies[0].DependsOn = append(doc.Dependencies[0].DependsOn, bomRef(dep))
doc.Dependencies = append(doc.Dependencies, dependency{
Ref: bomRef(dep.Path, dep.Version),
Ref: bomRef(dep),
})

doc.Compositions[1].Dependencies = append(doc.Compositions[1].Dependencies, bomRef(dep.Path, dep.Version))
doc.Compositions[1].Dependencies = append(doc.Compositions[1].Dependencies, bomRef(dep))
}

var buf bytes.Buffer
Expand All @@ -150,6 +125,10 @@ func GenerateCycloneDX(mod []byte) ([]byte, error) {
return buf.Bytes(), nil
}

func GenerateIndexCycloneDX(sii oci.SignedImageIndex) ([]byte, error) {
return nil, nil
}

type document struct {
BOMFormat string `json:"bomFormat"`
SpecVersion string `json:"specVersion"`
Expand Down
28 changes: 27 additions & 1 deletion internal/sbom/mod.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

// TODO: All of this is copied from:
// TODO: Most of this is copied from:
// https://cs.opensource.google/go/go/+/master:src/debug/buildinfo/buildinfo.go
// https://cs.opensource.google/go/go/+/master:src/runtime/debug/mod.go
// It should be replaced with runtime/buildinfo.Read on the binary file when Go 1.18 is released.
Expand All @@ -28,6 +28,32 @@ import (
"strings"
)

func modulePackageName(mod *Module) string {
return fmt.Sprintf("SPDXRef-Package-%s-%s",
strings.ReplaceAll(mod.Path, "/", "."),
mod.Version)
}

func bomRef(mod *Module) string {
return fmt.Sprintf("pkg:golang/%s@%s?type=module", mod.Path, mod.Version)
}

func goRef(mod *Module) string {
path := mod.Path
// Try to lowercase the first 2 path elements to comply with spec
// https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang
p := strings.Split(path, "/")
if len(p) > 2 {
path = strings.Join(
append(
[]string{strings.ToLower(p[0]), strings.ToLower(p[1])},
p[2:]...,
), "/",
)
}
return fmt.Sprintf("pkg:golang/%s@%s?type=module", path, mod.Version)
}

// BuildInfo represents the build information read from a Go binary.
// https://cs.opensource.google/go/go/+/release-branch.go1.18:src/runtime/debug/mod.go;drc=release-branch.go1.18;l=41
type BuildInfo struct {
Expand Down
27 changes: 27 additions & 0 deletions internal/sbom/mod_1.18.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package sbom
import (
"fmt"
"runtime/debug"
"strings"
)

type BuildInfo debug.BuildInfo
Expand All @@ -32,3 +33,29 @@ func ParseBuildInfo(data string) (*BuildInfo, error) {
bi := BuildInfo(*dbi)
return &bi, nil
}

func modulePackageName(mod *debug.Module) string {
Copy link
Member

Choose a reason for hiding this comment

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

Should these be in some shared file that both 1.18 and pre-1.18 code can reuse?

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 had to move them here because I wanted to use the *debug.Module type :-/

Copy link
Member

Choose a reason for hiding this comment

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

Boooo I guess what you had was fine. I hate this pre-1.18 and count down the days until we don't have to support it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah, I had no idea until CI blew up on me, and I had to contort things to be able to leverage module types.

Will be good to kill this with 🔥

return fmt.Sprintf("SPDXRef-Package-%s-%s",
strings.ReplaceAll(mod.Path, "/", "."),
mod.Version)
}

func bomRef(mod *debug.Module) string {
return fmt.Sprintf("pkg:golang/%s@%s?type=module", mod.Path, mod.Version)
}

func goRef(mod *debug.Module) string {
path := mod.Path
// Try to lowercase the first 2 path elements to comply with spec
// https://github.com/package-url/purl-spec/blob/master/PURL-TYPES.rst#golang
p := strings.Split(path, "/")
if len(p) > 2 {
path = strings.Join(
append(
[]string{strings.ToLower(p[0]), strings.ToLower(p[1])},
p[2:]...,
), "/",
)
}
return fmt.Sprintf("pkg:golang/%s@%s?type=module", path, mod.Version)
}