diff --git a/.github/workflows/sbom.yaml b/.github/workflows/sbom.yaml index 2e89fcc37e..4f79cfbe4a 100644 --- a/.github/workflows/sbom.yaml +++ b/.github/workflows/sbom.yaml @@ -9,38 +9,6 @@ env: SPDX_TOOLS_VERSION: 1.1.0 jobs: - cyclonedx: - name: Validate CycloneDX SBOM - runs-on: ubuntu-latest - - env: - KO_DOCKER_REPO: localhost:1338 - - steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 - with: - go-version-file: 'go.mod' - check-latest: true - - uses: chainguard-dev/actions/setup-registry@main - - uses: sigstore/cosign-installer@59acb6260d9c0ba8f4a2f9d9b48431a222b68e20 # v3.5.0 - - - name: Install CycloneDX - run: | - wget https://github.com/CycloneDX/cyclonedx-cli/releases/download/v0.22.0/cyclonedx-linux-x64 - chmod +x cyclonedx-linux-x64 - - - name: Generate and Validate - run: | - cosign download sbom $(go run ./ build --sbom=cyclonedx) | tee cyclonedx.json - ./cyclonedx-linux-x64 validate --input-file=cyclonedx.json --fail-on-errors - - - uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808 # v4.3.3 - if: ${{ always() }} - with: - name: cyclonedx.json - path: cyclonedx.json - spdx: name: Validate SPDX SBOM runs-on: ubuntu-latest diff --git a/docs/features/sboms.md b/docs/features/sboms.md index 2730e622e0..2ba92b50b4 100644 --- a/docs/features/sboms.md +++ b/docs/features/sboms.md @@ -5,7 +5,7 @@ Having a list of dependencies can be helpful in determining whether any vulnerab **From v0.9+, `ko` generates and uploads an SBOM for every image it produces by default.** -ko will generate an SBOM in the [SPDX](https://spdx.dev/) format by default, but you can select the [CycloneDX](https://cyclonedx.org/) format instead with the `--sbom=cyclonedx` flag. To disable SBOM generation, pass `--sbom=none`. +ko will generate an SBOM in the [SPDX](https://spdx.dev/) format by default. To disable SBOM generation, pass `--sbom=none`. These SBOMs can be downloaded using the [`cosign download sbom`](https://github.com/sigstore/cosign/blob/main/doc/cosign_download_sbom.md) command. diff --git a/docs/reference/ko_apply.md b/docs/reference/ko_apply.md index b492120985..acadd81846 100644 --- a/docs/reference/ko_apply.md +++ b/docs/reference/ko_apply.md @@ -60,7 +60,7 @@ ko apply -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, also supports: spdx, cyclonedx, go.version-m). (default "spdx") + --sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "spdx") --sbom-dir string Path to file where the SBOM will be written. -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. diff --git a/docs/reference/ko_build.md b/docs/reference/ko_build.md index 8c4d97d6e1..2dc2684b56 100644 --- a/docs/reference/ko_build.md +++ b/docs/reference/ko_build.md @@ -55,7 +55,7 @@ ko build IMPORTPATH... [flags] --platform strings Which platform to use when pulling a multi-platform base. Format: all | [/[/]][,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, also supports: spdx, cyclonedx, go.version-m). (default "spdx") + --sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "spdx") --sbom-dir string Path to file where the SBOM will be written. --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]) diff --git a/docs/reference/ko_create.md b/docs/reference/ko_create.md index 700f94340c..a9f5d0a448 100644 --- a/docs/reference/ko_create.md +++ b/docs/reference/ko_create.md @@ -60,7 +60,7 @@ ko create -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, also supports: spdx, cyclonedx, go.version-m). (default "spdx") + --sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "spdx") --sbom-dir string Path to file where the SBOM will be written. -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. diff --git a/docs/reference/ko_resolve.md b/docs/reference/ko_resolve.md index 9466bbb308..0de0cdcceb 100644 --- a/docs/reference/ko_resolve.md +++ b/docs/reference/ko_resolve.md @@ -53,7 +53,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, also supports: spdx, cyclonedx, go.version-m). (default "spdx") + --sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "spdx") --sbom-dir string Path to file where the SBOM will be written. -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. diff --git a/docs/reference/ko_run.md b/docs/reference/ko_run.md index 5e2c88b045..6983a51081 100644 --- a/docs/reference/ko_run.md +++ b/docs/reference/ko_run.md @@ -43,7 +43,7 @@ ko run IMPORTPATH [flags] --platform strings Which platform to use when pulling a multi-platform base. Format: all | [/[/]][,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, also supports: spdx, cyclonedx, go.version-m). (default "spdx") + --sbom string The SBOM media type to use (none will disable SBOM synthesis and upload). (default "spdx") --sbom-dir string Path to file where the SBOM will be written. --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]) diff --git a/internal/sbom/cyclonedx.go b/internal/sbom/cyclonedx.go deleted file mode 100644 index 0e4728d645..0000000000 --- a/internal/sbom/cyclonedx.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright 2022 ko Build Authors All Rights Reserved. -// -// 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. - -package sbom - -import ( - "bytes" - "encoding/base64" - "encoding/hex" - "encoding/json" - "runtime/debug" - "strings" - - "github.com/sigstore/cosign/v2/pkg/oci" -) - -func h1ToSHA256(s string) string { - if !strings.HasPrefix(s, "h1:") { - return "" - } - b, err := base64.StdEncoding.DecodeString(s[3:]) - if err != nil { - return "" - } - return hex.EncodeToString(b) -} - -func GenerateImageCycloneDX(mod []byte) ([]byte, error) { - var err error - mod, err = massageGoVersionM(mod) - if err != nil { - return nil, err - } - - bi, err := debug.ParseBuildInfo(string(mod)) - if err != nil { - return nil, err - } - - doc := document{ - BOMFormat: "CycloneDX", - SpecVersion: "1.4", - Version: 1, - Metadata: metadata{ - Component: component{ - BOMRef: bomRef(&bi.Main), - Type: "application", - Name: bi.Main.Path, - Version: bi.Main.Version, - Purl: bomRef(&bi.Main), - ExternalReferences: []externalReference{{ - URL: "https://" + bi.Main.Path, - Type: "vcs", - }}, - }, - Properties: []property{{ - Name: "cdx:gomod:binary:name", - Value: "out", - }}, - // TODO: include all hashes - // TODO: include go version - // TODO: include bi.Settings? - }, - Dependencies: []dependency{{ - Ref: bomRef(&bi.Main), - }}, - Compositions: []composition{{ - Aggregate: "complete", - Dependencies: []string{ - bomRef(&bi.Main), - }, - }, { - Aggregate: "unknown", - Dependencies: []string{}, - }}, - } - for _, dep := range bi.Deps { - // Don't include replaced deps - if dep.Replace != nil { - continue - } - comp := component{ - BOMRef: bomRef(dep), - Type: "library", - Name: dep.Path, - Version: dep.Version, - Scope: "required", - Purl: bomRef(dep), - ExternalReferences: []externalReference{{ - URL: "https://" + dep.Path, - Type: "vcs", - }}, - } - if dep.Sum != "" { - comp.Hashes = []hash{{ - Alg: "SHA-256", - Content: h1ToSHA256(dep.Sum), - }} - } - doc.Components = append(doc.Components, comp) - doc.Dependencies[0].DependsOn = append(doc.Dependencies[0].DependsOn, bomRef(dep)) - doc.Dependencies = append(doc.Dependencies, dependency{ - Ref: bomRef(dep), - }) - - doc.Compositions[1].Dependencies = append(doc.Compositions[1].Dependencies, bomRef(dep)) - } - - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - enc.SetIndent("", " ") - if err := enc.Encode(doc); err != nil { - return nil, err - } - return buf.Bytes(), nil -} - -func GenerateIndexCycloneDX(oci.SignedImageIndex) ([]byte, error) { - return nil, nil -} - -type document struct { - BOMFormat string `json:"bomFormat"` - SpecVersion string `json:"specVersion"` - Version int `json:"version"` - Metadata metadata `json:"metadata"` - Components []component `json:"components,omitempty"` - Dependencies []dependency `json:"dependencies,omitempty"` - Compositions []composition `json:"compositions,omitempty"` -} -type metadata struct { - Component component `json:"component"` - Properties []property `json:"properties,omitempty"` -} -type component struct { - BOMRef string `json:"bom-ref"` - Type string `json:"type"` - Name string `json:"name"` - Version string `json:"version"` - Scope string `json:"scope,omitempty"` - Hashes []hash `json:"hashes,omitempty"` - Purl string `json:"purl"` - ExternalReferences []externalReference `json:"externalReferences"` -} -type hash struct { - Alg string `json:"alg"` - Content string `json:"content"` -} -type externalReference struct { - URL string `json:"url"` - Type string `json:"type"` -} -type property struct { - Name string `json:"name"` - Value string `json:"value"` -} -type dependency struct { - Ref string `json:"ref"` - DependsOn []string `json:"dependsOn,omitempty"` -} -type composition struct { - Aggregate string `json:"aggregate"` - Dependencies []string `json:"dependencies,omitempty"` -} diff --git a/internal/sbom/spdx.go b/internal/sbom/spdx.go index 809c14e5dd..4d2c46c135 100644 --- a/internal/sbom/spdx.go +++ b/internal/sbom/spdx.go @@ -16,6 +16,8 @@ package sbom import ( "bytes" + "encoding/base64" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -51,6 +53,17 @@ func ociRef(path string, imgDigest v1.Hash, qual ...qualifier) string { return purl } +func h1ToSHA256(s string) string { + if !strings.HasPrefix(s, "h1:") { + return "" + } + b, err := base64.StdEncoding.DecodeString(s[3:]) + if err != nil { + return "" + } + return hex.EncodeToString(b) +} + const dateFormat = "2006-01-02T15:04:05Z" func GenerateImageSPDX(koVersion string, mod []byte, img oci.SignedImage) ([]byte, error) { diff --git a/main.go b/main.go index 6eda5d6307..ad1d54ef77 100644 --- a/main.go +++ b/main.go @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +//go:generate go run ./cmd/help/main.go -d docs/reference/ package main import ( diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go index 0bc44cbfc8..ac18fe41bf 100644 --- a/pkg/build/gobuild.go +++ b/pkg/build/gobuild.go @@ -454,44 +454,6 @@ func writeSBOM(sbom []byte, appFileName, dir, ext string) error { return nil } -func cycloneDX() sbomber { - return func(ctx context.Context, file string, appPath string, appFileName string, se oci.SignedEntity, dir string) ([]byte, types.MediaType, error) { - switch obj := se.(type) { - case oci.SignedImage: - b, _, err := goversionm(ctx, file, appPath, appFileName, obj, "") - if err != nil { - return nil, "", err - } - - b, err = sbom.GenerateImageCycloneDX(b) - if err != nil { - return nil, "", err - } - - if err := writeSBOM(b, appFileName, dir, "cyclonedx.json"); err != nil { - return nil, "", err - } - - return b, ctypes.CycloneDXJSONMediaType, nil - - case oci.SignedImageIndex: - b, err := sbom.GenerateIndexCycloneDX(obj) - if err != nil { - return nil, "", err - } - - if err := writeSBOM(b, appFileName, dir, "cyclonedx.json"); err != nil { - return nil, "", err - } - - return b, ctypes.SPDXJSONMediaType, err - - default: - return nil, "", fmt.Errorf("unrecognized type: %T", se) - } - } -} - // buildEnv creates the environment variables used by the `go build` command. // From `os/exec.Cmd`: If there are duplicate environment keys, only the last // value in the slice for each duplicate key is used. diff --git a/pkg/build/options.go b/pkg/build/options.go index c09aa16d4a..42f0473d8b 100644 --- a/pkg/build/options.go +++ b/pkg/build/options.go @@ -151,15 +151,6 @@ 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 { @@ -169,15 +160,6 @@ func WithSPDX(version string) Option { } } -// WithCycloneDX is a functional option to direct ko to use CycloneDX for SBOM -// format. -func WithCycloneDX() Option { - return func(gbo *gobuildOpener) error { - gbo.sbom = cycloneDX() - return nil - } -} - // withSBOMber is a functional option for overriding the way SBOMs // are generated. func withSBOMber(sbom sbomber) Option { diff --git a/pkg/commands/options/build.go b/pkg/commands/options/build.go index c02eda6f26..18e2f6585c 100644 --- a/pkg/commands/options/build.go +++ b/pkg/commands/options/build.go @@ -87,7 +87,7 @@ func AddBuildOptions(cmd *cobra.Command, bo *BuildOptions) { 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", "spdx", - "The SBOM media type to use (none will disable SBOM synthesis and upload, also supports: spdx, cyclonedx, go.version-m).") + "The SBOM media type to use (none will disable SBOM synthesis and upload).") cmd.Flags().StringVar(&bo.SBOMDir, "sbom-dir", "", "Path to file where the SBOM will be written.") cmd.Flags().StringSliceVar(&bo.Platforms, "platform", []string{}, diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 7d890a9bc3..c8bf031594 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -104,10 +104,6 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) { switch bo.SBOM { case "none": opts = append(opts, build.WithDisabledSBOM()) - case "go.version-m": - opts = append(opts, build.WithGoVersionSBOM()) - case "cyclonedx": - opts = append(opts, build.WithCycloneDX()) default: // "spdx" opts = append(opts, build.WithSPDX(version())) }