Skip to content

Commit

Permalink
Connect SBOMs with SPDX support. (#511)
Browse files Browse the repository at this point in the history
* Connect SBOMs with SPDX support.

This combines Jason's SPDX stuff and my SBOM stuff to support
SPDX-based SBOMs by default instead of our `go version -m`
invention.

* Make ko deps use SPDX by default
  • Loading branch information
mattmoor authored Nov 22, 2021
1 parent af2ff52 commit 3edb68b
Show file tree
Hide file tree
Showing 13 changed files with 74 additions and 32 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/kind-e2e.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ jobs:
run: |
set -o pipefail
IMAGE=$(ko publish ./test)
IMAGE=$(ko build ./test)
SBOM=$(cosign download sbom ${IMAGE})
KO_DEPS=$(ko deps ${IMAGE})
Expand Down
2 changes: 1 addition & 1 deletion doc/ko_apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ ko apply -f FILENAME [flags]
--push Push images to KO_DOCKER_REPO (default true)
-R, --recursive Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (DEPRECATED)
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "go.version-m")
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, go.version-m). (default "spdx")
-l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
-s, --server string The address and port of the Kubernetes API server (DEPRECATED)
--tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
Expand Down
2 changes: 1 addition & 1 deletion doc/ko_build.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ ko build IMPORTPATH... [flags]
--platform string Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*
-P, --preserve-import-paths Whether to preserve the full import path after KO_DOCKER_REPO.
--push Push images to KO_DOCKER_REPO (default true)
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "go.version-m")
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, go.version-m). (default "spdx")
--tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
-t, --tags strings Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
--tarball string File to save images tarballs
Expand Down
2 changes: 1 addition & 1 deletion doc/ko_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ ko create -f FILENAME [flags]
--push Push images to KO_DOCKER_REPO (default true)
-R, --recursive Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
--request-timeout string The length of time to wait before giving up on a single server request. Non-zero values should contain a corresponding time unit (e.g. 1s, 2m, 3h). A value of zero means don't timeout requests. (DEPRECATED)
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "go.version-m")
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, go.version-m). (default "spdx")
-l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
-s, --server string The address and port of the Kubernetes API server (DEPRECATED)
--tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
Expand Down
2 changes: 1 addition & 1 deletion doc/ko_deps.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ ko deps IMAGE [flags]

```
-h, --help help for deps
--sbom string Format for SBOM output (default "go.version-m")
--sbom string Format for SBOM output (supports: spdx, go.version-m). (default "spdx")
```

### SEE ALSO
Expand Down
2 changes: 1 addition & 1 deletion doc/ko_resolve.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ ko resolve -f FILENAME [flags]
-P, --preserve-import-paths Whether to preserve the full import path after KO_DOCKER_REPO.
--push Push images to KO_DOCKER_REPO (default true)
-R, --recursive Process the directory used in -f, --filename recursively. Useful when you want to manage related manifests organized within the same directory.
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "go.version-m")
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, go.version-m). (default "spdx")
-l, --selector string Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)
--tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
-t, --tags strings Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
Expand Down
2 changes: 1 addition & 1 deletion doc/ko_run.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ ko run IMPORTPATH [flags]
--platform string Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*
-P, --preserve-import-paths Whether to preserve the full import path after KO_DOCKER_REPO.
--push Push images to KO_DOCKER_REPO (default true)
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "go.version-m")
--sbom string The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, go.version-m). (default "spdx")
--tag-only Include tags but not digests in resolved image references. Useful when digests are not preserved when images are repopulated.
-t, --tags strings Which tags to use for the produced image instead of the default 'latest' tag (may not work properly with --base-import-paths or --bare). (default [latest])
--tarball string File to save images tarballs
Expand Down
60 changes: 40 additions & 20 deletions pkg/build/gobuild.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,13 @@ import (
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/google/go-containerregistry/pkg/v1/types"
"github.com/google/ko/internal/sbom"
specsv1 "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/sigstore/cosign/pkg/oci"
ocimutate "github.com/sigstore/cosign/pkg/oci/mutate"
"github.com/sigstore/cosign/pkg/oci/signed"
"github.com/sigstore/cosign/pkg/oci/static"
ctypes "github.com/sigstore/cosign/pkg/types"
"golang.org/x/tools/go/packages"
)

Expand All @@ -57,7 +59,7 @@ const (
type GetBase func(context.Context, string) (name.Reference, Result, error)

type builder func(context.Context, string, string, v1.Platform, Config) (string, error)
type sbomber func(context.Context, string, string) ([]byte, types.MediaType, error)
type sbomber func(context.Context, string, string, v1.Image) ([]byte, types.MediaType, error)

type platformMatcher struct {
spec string
Expand All @@ -71,7 +73,6 @@ type gobuild struct {
build builder
sbom sbomber
disableOptimizations bool
disableSBOM bool
trimpath bool
buildConfigs map[string]Config
platformMatcher *platformMatcher
Expand All @@ -89,7 +90,6 @@ type gobuildOpener struct {
build builder
sbom sbomber
disableOptimizations bool
disableSBOM bool
trimpath bool
buildConfigs map[string]Config
platform string
Expand All @@ -112,7 +112,6 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
build: gbo.build,
sbom: gbo.sbom,
disableOptimizations: gbo.disableOptimizations,
disableSBOM: gbo.disableSBOM,
trimpath: gbo.trimpath,
buildConfigs: gbo.buildConfigs,
labels: gbo.labels,
Expand All @@ -130,8 +129,8 @@ func (gbo *gobuildOpener) Open() (Interface, error) {
func NewGo(ctx context.Context, dir string, options ...Option) (Interface, error) {
gbo := &gobuildOpener{
build: build,
sbom: sbom,
dir: dir,
sbom: spdx("(none)"),
}

for _, option := range options {
Expand Down Expand Up @@ -261,7 +260,7 @@ func build(ctx context.Context, ip string, dir string, platform v1.Platform, con
return file, nil
}

func sbom(ctx context.Context, file string, appPath string) ([]byte, types.MediaType, error) {
func goversionm(ctx context.Context, file string, appPath string, _ v1.Image) ([]byte, types.MediaType, error) {
sbom := bytes.NewBuffer(nil)
cmd := exec.CommandContext(ctx, "go", "version", "-m", file)
cmd.Stdout = sbom
Expand All @@ -270,14 +269,31 @@ func sbom(ctx context.Context, file string, appPath string) ([]byte, types.Media
return nil, "", err
}

// TODO(imjasonh): Turn the output of `go version -m` on
// file into a standard format and attach here.

// In order to get deterministics SBOMs replace our randomized
// file name with the path the app will get inside of the container.
return []byte(strings.Replace(sbom.String(), file, appPath, 1)), "application/vnd.go.version-m", nil
}

func spdx(version string) sbomber {
return func(ctx context.Context, file string, appPath string, img v1.Image) ([]byte, types.MediaType, error) {
b, _, err := goversionm(ctx, file, appPath, img)
if err != nil {
return nil, "", err
}

cfg, err := img.ConfigFile()
if err != nil {
return nil, "", err
}

b, err = sbom.GenerateSPDX(version, cfg.Created.Time, b)
if err != nil {
return nil, "", err
}
return b, ctypes.SPDXMediaType, nil
}
}

// buildEnv creates the environment variables used by the `go build` command.
// From `os/exec.Cmd`: If Env contains duplicate environment keys, only the last
// value in the slice for each duplicate key is used.
Expand Down Expand Up @@ -724,19 +740,23 @@ func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, pl
}
}

if g.disableSBOM {
return signed.Image(image), nil
}
si := signed.Image(image)

sbom, mt, err := g.sbom(ctx, file, appPath)
if err != nil {
return nil, err
}
f, err := static.NewFile(sbom, static.WithLayerMediaType(mt))
if err != nil {
return nil, err
if g.sbom != nil {
sbom, mt, err := g.sbom(ctx, file, appPath, image)
if err != nil {
return nil, err
}
f, err := static.NewFile(sbom, static.WithLayerMediaType(mt))
if err != nil {
return nil, err
}
si, err = ocimutate.AttachFileToImage(si, "sbom", f)
if err != nil {
return nil, err
}
}
return ocimutate.AttachFileToImage(signed.Image(image), "sbom", f)
return si, nil
}

// Append appPath to the PATH environment variable, if it exists. Otherwise,
Expand Down
2 changes: 1 addition & 1 deletion pkg/build/gobuild_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -392,7 +392,7 @@ func nilGetBase(_ context.Context, _ string) (name.Reference, Result, error) {
const wantSBOM = "This is our fake SBOM"

// A helper method we use to substitute for the default "build" method.
func fauxSBOM(_ context.Context, _ string, _ string) ([]byte, types.MediaType, error) {
func fauxSBOM(_ context.Context, _ string, _ string, _ v1.Image) ([]byte, types.MediaType, error) {
return []byte(wantSBOM), "application/vnd.garbage", nil
}

Expand Down
20 changes: 19 additions & 1 deletion pkg/build/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func WithDisabledOptimizations() Option {
// WithDisabledSBOM is a functional option for disabling SBOM generation.
func WithDisabledSBOM() Option {
return func(gbo *gobuildOpener) error {
gbo.disableSBOM = true
gbo.sbom = nil
return nil
}
}
Expand Down Expand Up @@ -116,6 +116,24 @@ func withBuilder(b builder) Option {
}
}

// WithGoVersionSBOM is a functional option to direct ko to use
// go version -m for SBOM format.
func WithGoVersionSBOM() Option {
return func(gbo *gobuildOpener) error {
gbo.sbom = goversionm
return nil
}
}

// WithSPDX is a functional option to direct ko to use
// SPDX for SBOM format.
func WithSPDX(version string) Option {
return func(gbo *gobuildOpener) error {
gbo.sbom = spdx(version)
return nil
}
}

// withSBOMber is a functional option for overriding the way SBOMs
// are generated.
func withSBOMber(sbom sbomber) Option {
Expand Down
2 changes: 1 addition & 1 deletion pkg/commands/deps.go
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,6 @@ If the image was not built using ko, or if it was built without embedding depend
// unreachable
},
}
deps.Flags().StringVar(&sbomType, "sbom", "go.version-m", "Format for SBOM output")
deps.Flags().StringVar(&sbomType, "sbom", "spdx", "Format for SBOM output (supports: spdx, go.version-m).")
topLevel.AddCommand(deps)
}
4 changes: 2 additions & 2 deletions pkg/commands/options/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,8 @@ func AddBuildOptions(cmd *cobra.Command, bo *BuildOptions) {
"The maximum number of concurrent builds (default GOMAXPROCS)")
cmd.Flags().BoolVar(&bo.DisableOptimizations, "disable-optimizations", bo.DisableOptimizations,
"Disable optimizations when building Go code. Useful when you want to interactively debug the created container.")
cmd.Flags().StringVar(&bo.SBOM, "sbom", "go.version-m",
"The SBOM media type to use (none will disable SBOM synthesis and upload).")
cmd.Flags().StringVar(&bo.SBOM, "sbom", "spdx",
"The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, go.version-m).")
cmd.Flags().StringVar(&bo.Platform, "platform", "",
"Which platform to use when pulling a multi-platform base. Format: all | <os>[/<arch>[/<variant>]][,platform]*")
cmd.Flags().StringSliceVar(&bo.Labels, "image-label", []string{},
Expand Down
4 changes: 4 additions & 0 deletions pkg/commands/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,10 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) {
switch bo.SBOM {
case "none":
opts = append(opts, build.WithDisabledSBOM())
case "spdx":
opts = append(opts, build.WithSPDX(version()))
case "go.version-m":
opts = append(opts, build.WithGoVersionSBOM())
}
opts = append(opts, build.WithTrimpath(bo.Trimpath))
for _, lf := range bo.Labels {
Expand Down

0 comments on commit 3edb68b

Please sign in to comment.