You've already forked goreleaser
mirror of
https://github.com/goreleaser/goreleaser.git
synced 2025-09-16 09:26:52 +02:00
feat: add sbom generation pipe (#2648)
Signed-off-by: Alex Goodman <alex.goodman@anchore.com> Co-authored-by: Carlos Alexandro Becker <caarlos0@users.noreply.github.com>
This commit is contained in:
4
.github/workflows/build.yml
vendored
4
.github/workflows/build.yml
vendored
@@ -81,6 +81,10 @@ jobs:
|
|||||||
./dist/*.apk
|
./dist/*.apk
|
||||||
key: ${{ runner.os }}-go-${{ hashFiles('**/*.go') }}-${{ hashFiles('**/go.sum') }}
|
key: ${{ runner.os }}-go-${{ hashFiles('**/*.go') }}-${{ hashFiles('**/go.sum') }}
|
||||||
- uses: sigstore/cosign-installer@v1.4.1
|
- uses: sigstore/cosign-installer@v1.4.1
|
||||||
|
- name: setup-syft
|
||||||
|
run: |
|
||||||
|
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | \
|
||||||
|
sh -s -- -b /usr/local/bin v0.32.0
|
||||||
- name: setup-validate-krew-manifest
|
- name: setup-validate-krew-manifest
|
||||||
# TODO: replace this once https://github.com/kubernetes-sigs/krew/pull/736 is merged
|
# TODO: replace this once https://github.com/kubernetes-sigs/krew/pull/736 is merged
|
||||||
run: go install github.com/caarlos0/krew/cmd/validate-krew-manifest@fork
|
run: go install github.com/caarlos0/krew/cmd/validate-krew-manifest@fork
|
||||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -11,3 +11,4 @@ www/docs/static/releases*.json
|
|||||||
completions/
|
completions/
|
||||||
.vscode/
|
.vscode/
|
||||||
.task/
|
.task/
|
||||||
|
.idea/
|
||||||
|
@@ -201,11 +201,19 @@ snapcrafts:
|
|||||||
confinement: classic
|
confinement: classic
|
||||||
publish: true
|
publish: true
|
||||||
|
|
||||||
|
sboms:
|
||||||
|
- artifacts: archive
|
||||||
|
|
||||||
signs:
|
signs:
|
||||||
- cmd: cosign
|
- cmd: cosign
|
||||||
stdin: '{{ .Env.COSIGN_PWD }}'
|
stdin: '{{ .Env.COSIGN_PWD }}'
|
||||||
args: ["sign-blob", "-key=cosign.key", "-output=${signature}", "${artifact}"]
|
args: ["sign-blob", "-key=cosign.key", "-output=${signature}", "${artifact}"]
|
||||||
artifacts: checksum
|
artifacts: checksum
|
||||||
|
- id: sign-sboms
|
||||||
|
cmd: cosign
|
||||||
|
stdin: '{{ .Env.COSIGN_PWD }}'
|
||||||
|
args: ["sign-blob", "-key=cosign.key", "-output=${signature}", "${artifact}"]
|
||||||
|
artifacts: sbom
|
||||||
|
|
||||||
docker_signs:
|
docker_signs:
|
||||||
- artifacts: manifests
|
- artifacts: manifests
|
||||||
|
@@ -35,6 +35,7 @@ type releaseOpts struct {
|
|||||||
skipSign bool
|
skipSign bool
|
||||||
skipValidate bool
|
skipValidate bool
|
||||||
skipAnnounce bool
|
skipAnnounce bool
|
||||||
|
skipSBOMCataloging bool
|
||||||
rmDist bool
|
rmDist bool
|
||||||
deprecated bool
|
deprecated bool
|
||||||
parallelism int
|
parallelism int
|
||||||
@@ -82,6 +83,7 @@ func newReleaseCmd() *releaseCmd {
|
|||||||
cmd.Flags().BoolVar(&root.opts.skipPublish, "skip-publish", false, "Skips publishing artifacts")
|
cmd.Flags().BoolVar(&root.opts.skipPublish, "skip-publish", false, "Skips publishing artifacts")
|
||||||
cmd.Flags().BoolVar(&root.opts.skipAnnounce, "skip-announce", false, "Skips announcing releases (implies --skip-validate)")
|
cmd.Flags().BoolVar(&root.opts.skipAnnounce, "skip-announce", false, "Skips announcing releases (implies --skip-validate)")
|
||||||
cmd.Flags().BoolVar(&root.opts.skipSign, "skip-sign", false, "Skips signing artifacts")
|
cmd.Flags().BoolVar(&root.opts.skipSign, "skip-sign", false, "Skips signing artifacts")
|
||||||
|
cmd.Flags().BoolVar(&root.opts.skipSBOMCataloging, "skip-sbom", false, "Skips cataloging artifacts")
|
||||||
cmd.Flags().BoolVar(&root.opts.skipValidate, "skip-validate", false, "Skips git checks")
|
cmd.Flags().BoolVar(&root.opts.skipValidate, "skip-validate", false, "Skips git checks")
|
||||||
cmd.Flags().BoolVar(&root.opts.rmDist, "rm-dist", false, "Removes the dist folder")
|
cmd.Flags().BoolVar(&root.opts.rmDist, "rm-dist", false, "Removes the dist folder")
|
||||||
cmd.Flags().IntVarP(&root.opts.parallelism, "parallelism", "p", 0, "Amount tasks to run concurrently (default: number of CPUs)")
|
cmd.Flags().IntVarP(&root.opts.parallelism, "parallelism", "p", 0, "Amount tasks to run concurrently (default: number of CPUs)")
|
||||||
@@ -139,6 +141,7 @@ func setupReleaseContext(ctx *context.Context, options releaseOpts) *context.Con
|
|||||||
ctx.SkipAnnounce = ctx.Snapshot || options.skipPublish || options.skipAnnounce
|
ctx.SkipAnnounce = ctx.Snapshot || options.skipPublish || options.skipAnnounce
|
||||||
ctx.SkipValidate = ctx.Snapshot || options.skipValidate
|
ctx.SkipValidate = ctx.Snapshot || options.skipValidate
|
||||||
ctx.SkipSign = options.skipSign
|
ctx.SkipSign = options.skipSign
|
||||||
|
ctx.SkipSBOMCataloging = options.skipSBOMCataloging
|
||||||
ctx.RmDist = options.rmDist
|
ctx.RmDist = options.rmDist
|
||||||
|
|
||||||
// test only
|
// test only
|
||||||
|
@@ -61,6 +61,8 @@ const (
|
|||||||
KrewPluginManifest
|
KrewPluginManifest
|
||||||
// ScoopManifest is an uploadable scoop manifest file.
|
// ScoopManifest is an uploadable scoop manifest file.
|
||||||
ScoopManifest
|
ScoopManifest
|
||||||
|
// SBOM is a Software Bill of Materials file.
|
||||||
|
SBOM
|
||||||
)
|
)
|
||||||
|
|
||||||
func (t Type) String() string {
|
func (t Type) String() string {
|
||||||
@@ -95,6 +97,8 @@ func (t Type) String() string {
|
|||||||
return "Krew Plugin Manifest"
|
return "Krew Plugin Manifest"
|
||||||
case ScoopManifest:
|
case ScoopManifest:
|
||||||
return "Scoop Manifest"
|
return "Scoop Manifest"
|
||||||
|
case SBOM:
|
||||||
|
return "SBOM"
|
||||||
default:
|
default:
|
||||||
return "unknown"
|
return "unknown"
|
||||||
}
|
}
|
||||||
|
@@ -385,6 +385,7 @@ func TestTypeToString(t *testing.T) {
|
|||||||
GoFishRig,
|
GoFishRig,
|
||||||
KrewPluginManifest,
|
KrewPluginManifest,
|
||||||
ScoopManifest,
|
ScoopManifest,
|
||||||
|
SBOM,
|
||||||
} {
|
} {
|
||||||
t.Run(a.String(), func(t *testing.T) {
|
t.Run(a.String(), func(t *testing.T) {
|
||||||
require.NotEqual(t, "unknown", a.String())
|
require.NotEqual(t, "unknown", a.String())
|
||||||
|
@@ -70,6 +70,7 @@ func refresh(ctx *context.Context, filepath string) error {
|
|||||||
artifact.ByType(artifact.UploadableBinary),
|
artifact.ByType(artifact.UploadableBinary),
|
||||||
artifact.ByType(artifact.UploadableSourceArchive),
|
artifact.ByType(artifact.UploadableSourceArchive),
|
||||||
artifact.ByType(artifact.LinuxPackage),
|
artifact.ByType(artifact.LinuxPackage),
|
||||||
|
artifact.ByType(artifact.SBOM),
|
||||||
)
|
)
|
||||||
if len(ctx.Config.Checksum.IDs) > 0 {
|
if len(ctx.Config.Checksum.IDs) > 0 {
|
||||||
filter = artifact.And(filter, artifact.ByIDs(ctx.Config.Checksum.IDs...))
|
filter = artifact.And(filter, artifact.ByIDs(ctx.Config.Checksum.IDs...))
|
||||||
|
282
internal/pipe/sbom/sbom.go
Normal file
282
internal/pipe/sbom/sbom.go
Normal file
@@ -0,0 +1,282 @@
|
|||||||
|
package sbom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/apex/log"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/gio"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/ids"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/logext"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/semerrgroup"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/tmpl"
|
||||||
|
"github.com/goreleaser/goreleaser/pkg/config"
|
||||||
|
"github.com/goreleaser/goreleaser/pkg/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Environment variables to pass through to exec
|
||||||
|
var passthroughEnvVars = []string{"HOME", "USER", "USERPROFILE", "TMPDIR", "TMP", "TEMP", "PATH"}
|
||||||
|
|
||||||
|
// Pipe that catalogs common artifacts as an SBOM.
|
||||||
|
type Pipe struct{}
|
||||||
|
|
||||||
|
func (Pipe) String() string { return "cataloging artifacts" }
|
||||||
|
func (Pipe) Skip(ctx *context.Context) bool {
|
||||||
|
return ctx.SkipSBOMCataloging || len(ctx.Config.SBOMs) == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Default sets the Pipes defaults.
|
||||||
|
func (Pipe) Default(ctx *context.Context) error {
|
||||||
|
ids := ids.New("sboms")
|
||||||
|
for i := range ctx.Config.SBOMs {
|
||||||
|
cfg := &ctx.Config.SBOMs[i]
|
||||||
|
if cfg.Cmd == "" {
|
||||||
|
cfg.Cmd = "syft"
|
||||||
|
}
|
||||||
|
if cfg.Artifacts == "" {
|
||||||
|
cfg.Artifacts = "archive"
|
||||||
|
}
|
||||||
|
if len(cfg.Documents) == 0 {
|
||||||
|
switch cfg.Artifacts {
|
||||||
|
case "binary":
|
||||||
|
cfg.Documents = []string{"{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"}
|
||||||
|
case "any":
|
||||||
|
cfg.Documents = []string{}
|
||||||
|
default:
|
||||||
|
cfg.Documents = []string{"{{ .ArtifactName }}.sbom"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.Cmd == "syft" {
|
||||||
|
if len(cfg.Args) == 0 {
|
||||||
|
cfg.Args = []string{"$artifact", "--file", "$document", "--output", "spdx-json"}
|
||||||
|
}
|
||||||
|
if len(cfg.Env) == 0 && cfg.Artifacts == "source" || cfg.Artifacts == "archive" {
|
||||||
|
cfg.Env = []string{
|
||||||
|
"SYFT_FILE_METADATA_CATALOGER_ENABLED=true",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if cfg.ID == "" {
|
||||||
|
cfg.ID = "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
if cfg.Artifacts != "any" && len(cfg.Documents) > 1 {
|
||||||
|
return fmt.Errorf("multiple SBOM outputs when artifacts=%q is unsupported", cfg.Artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
ids.Inc(cfg.ID)
|
||||||
|
}
|
||||||
|
return ids.Validate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the Pipe.
|
||||||
|
func (Pipe) Run(ctx *context.Context) error {
|
||||||
|
g := semerrgroup.New(ctx.Parallelism)
|
||||||
|
for _, cfg := range ctx.Config.SBOMs {
|
||||||
|
g.Go(catalogTask(ctx, cfg))
|
||||||
|
}
|
||||||
|
return g.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
func catalogTask(ctx *context.Context, cfg config.SBOM) func() error {
|
||||||
|
return func() error {
|
||||||
|
var filters []artifact.Filter
|
||||||
|
switch cfg.Artifacts {
|
||||||
|
case "source":
|
||||||
|
filters = append(filters, artifact.ByType(artifact.UploadableSourceArchive))
|
||||||
|
if len(cfg.IDs) > 0 {
|
||||||
|
log.Warn("when artifacts is `source`, `ids` has no effect. ignoring")
|
||||||
|
}
|
||||||
|
case "archive":
|
||||||
|
filters = append(filters, artifact.ByType(artifact.UploadableArchive))
|
||||||
|
case "binary":
|
||||||
|
filters = append(filters, artifact.Or(
|
||||||
|
artifact.ByType(artifact.Binary),
|
||||||
|
artifact.ByType(artifact.UploadableBinary),
|
||||||
|
artifact.ByType(artifact.UniversalBinary),
|
||||||
|
))
|
||||||
|
case "package":
|
||||||
|
filters = append(filters, artifact.ByType(artifact.LinuxPackage))
|
||||||
|
case "any":
|
||||||
|
newArtifacts, err := catalogArtifact(ctx, cfg, nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, newArtifact := range newArtifacts {
|
||||||
|
ctx.Artifacts.Add(newArtifact)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("invalid list of artifacts to catalog: %s", cfg.Artifacts)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(cfg.IDs) > 0 {
|
||||||
|
filters = append(filters, artifact.ByIDs(cfg.IDs...))
|
||||||
|
}
|
||||||
|
artifacts := ctx.Artifacts.Filter(artifact.And(filters...)).List()
|
||||||
|
return catalog(ctx, cfg, artifacts)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func catalog(ctx *context.Context, cfg config.SBOM, artifacts []*artifact.Artifact) error {
|
||||||
|
for _, a := range artifacts {
|
||||||
|
newArtifacts, err := catalogArtifact(ctx, cfg, a)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, newArtifact := range newArtifacts {
|
||||||
|
ctx.Artifacts.Add(newArtifact)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func subprocessDistPath(distDir string, pathRelativeToCwd string) (string, error) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !filepath.IsAbs(distDir) {
|
||||||
|
distDir, err = filepath.Abs(distDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
relativePath, err := filepath.Rel(cwd, distDir)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return strings.TrimPrefix(pathRelativeToCwd, relativePath+string(filepath.Separator)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func catalogArtifact(ctx *context.Context, cfg config.SBOM, a *artifact.Artifact) ([]*artifact.Artifact, error) {
|
||||||
|
env := ctx.Env.Copy()
|
||||||
|
artifactDisplayName := "(any)"
|
||||||
|
templater := tmpl.New(ctx).WithEnv(env)
|
||||||
|
|
||||||
|
if a != nil {
|
||||||
|
procPath, err := subprocessDistPath(ctx.Config.Dist, a.Path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts failed: cannot determine artifact path for %q: %w", a.Path, err)
|
||||||
|
}
|
||||||
|
env["artifact"] = procPath
|
||||||
|
env["artifactID"] = a.ID()
|
||||||
|
|
||||||
|
templater = templater.WithArtifact(a, nil)
|
||||||
|
artifactDisplayName = a.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
var paths []string
|
||||||
|
for idx, sbom := range cfg.Documents {
|
||||||
|
input := filepath.Join(ctx.Config.Dist, expand(sbom, env))
|
||||||
|
|
||||||
|
path, err := templater.Apply(input)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts failed: %s: invalid template: %w", input, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
path, err = filepath.Abs(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts failed: unable to create artifact path %q: %w", sbom, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
procPath, err := subprocessDistPath(ctx.Config.Dist, path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts failed: cannot determine document path for %q: %w", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
env[fmt.Sprintf("document%d", idx)] = procPath
|
||||||
|
if idx == 0 {
|
||||||
|
env["document"] = procPath
|
||||||
|
}
|
||||||
|
|
||||||
|
paths = append(paths, procPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var names []string
|
||||||
|
for _, p := range paths {
|
||||||
|
names = append(names, filepath.Base(p))
|
||||||
|
}
|
||||||
|
|
||||||
|
fields := log.Fields{"cmd": cfg.Cmd, "artifact": artifactDisplayName, "sboms": strings.Join(names, ", ")}
|
||||||
|
|
||||||
|
// nolint:prealloc
|
||||||
|
var args []string
|
||||||
|
for _, arg := range cfg.Args {
|
||||||
|
renderedArg, err := templater.Apply(expand(arg, env))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts failed: %s: invalid template: %w", arg, err)
|
||||||
|
}
|
||||||
|
args = append(args, renderedArg)
|
||||||
|
}
|
||||||
|
|
||||||
|
// The GoASTScanner flags this as a security risk.
|
||||||
|
// However, this works as intended. The nosec annotation
|
||||||
|
// tells the scanner to ignore this.
|
||||||
|
// #nosec
|
||||||
|
cmd := exec.CommandContext(ctx, cfg.Cmd, args...)
|
||||||
|
cmd.Env = []string{}
|
||||||
|
for _, key := range passthroughEnvVars {
|
||||||
|
if value := os.Getenv(key); value != "" {
|
||||||
|
cmd.Env = append(cmd.Env, fmt.Sprintf("%s=%s", key, value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cmd.Env = append(cmd.Env, cfg.Env...)
|
||||||
|
cmd.Dir = ctx.Config.Dist
|
||||||
|
|
||||||
|
var b bytes.Buffer
|
||||||
|
w := gio.Safe(&b)
|
||||||
|
cmd.Stderr = io.MultiWriter(logext.NewWriter(fields, logext.Error), w)
|
||||||
|
cmd.Stdout = io.MultiWriter(logext.NewWriter(fields, logext.Info), w)
|
||||||
|
|
||||||
|
log.WithFields(fields).Info("cataloging")
|
||||||
|
if err := cmd.Run(); err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts: %s failed: %w: %s", cfg.Cmd, err, b.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
var artifacts []*artifact.Artifact
|
||||||
|
|
||||||
|
for _, sbom := range cfg.Documents {
|
||||||
|
templater = tmpl.New(ctx).WithEnv(env)
|
||||||
|
if a != nil {
|
||||||
|
env["artifact"] = a.Name
|
||||||
|
templater = templater.WithArtifact(a, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
name, err := templater.Apply(expand(sbom, env))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts failed: %s: invalid template: %w", a, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
search := filepath.Join(ctx.Config.Dist, name)
|
||||||
|
matches, err := filepath.Glob(search)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("cataloging artifacts: failed to find SBOM artifact %q: %w", search, err)
|
||||||
|
}
|
||||||
|
for _, match := range matches {
|
||||||
|
artifacts = append(artifacts, &artifact.Artifact{
|
||||||
|
Type: artifact.SBOM,
|
||||||
|
Name: name,
|
||||||
|
Path: match,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
artifact.ExtraID: cfg.ID,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return artifacts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func expand(s string, env map[string]string) string {
|
||||||
|
return os.Expand(s, func(key string) string {
|
||||||
|
return env[key]
|
||||||
|
})
|
||||||
|
}
|
593
internal/pipe/sbom/sbom_test.go
Normal file
593
internal/pipe/sbom/sbom_test.go
Normal file
@@ -0,0 +1,593 @@
|
|||||||
|
package sbom
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"sort"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||||
|
|
||||||
|
"github.com/goreleaser/goreleaser/pkg/config"
|
||||||
|
"github.com/goreleaser/goreleaser/pkg/context"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDescription(t *testing.T) {
|
||||||
|
require.NotEmpty(t, Pipe{}.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSBOMCatalogDefault(t *testing.T) {
|
||||||
|
defaultArgs := []string{"$artifact", "--file", "$document", "--output", "spdx-json"}
|
||||||
|
defaultSboms := []string{
|
||||||
|
"{{ .ArtifactName }}.sbom",
|
||||||
|
}
|
||||||
|
defaultCmd := "syft"
|
||||||
|
tests := []struct {
|
||||||
|
configs []config.SBOM
|
||||||
|
artifact string
|
||||||
|
cmd string
|
||||||
|
sboms []string
|
||||||
|
args []string
|
||||||
|
env []string
|
||||||
|
err bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
// empty
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artifact: "archive",
|
||||||
|
cmd: defaultCmd,
|
||||||
|
sboms: defaultSboms,
|
||||||
|
args: defaultArgs,
|
||||||
|
env: []string{
|
||||||
|
"SYFT_FILE_METADATA_CATALOGER_ENABLED=true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "package",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artifact: "package",
|
||||||
|
cmd: defaultCmd,
|
||||||
|
sboms: defaultSboms,
|
||||||
|
args: defaultArgs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "archive",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artifact: "archive",
|
||||||
|
cmd: defaultCmd,
|
||||||
|
sboms: defaultSboms,
|
||||||
|
args: defaultArgs,
|
||||||
|
env: []string{
|
||||||
|
"SYFT_FILE_METADATA_CATALOGER_ENABLED=true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "any",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artifact: "any",
|
||||||
|
cmd: defaultCmd,
|
||||||
|
sboms: []string{},
|
||||||
|
args: defaultArgs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "binary",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artifact: "binary",
|
||||||
|
cmd: defaultCmd,
|
||||||
|
sboms: []string{"{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"},
|
||||||
|
args: defaultArgs,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "source",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
artifact: "source",
|
||||||
|
cmd: defaultCmd,
|
||||||
|
sboms: defaultSboms,
|
||||||
|
args: defaultArgs,
|
||||||
|
env: []string{
|
||||||
|
"SYFT_FILE_METADATA_CATALOGER_ENABLED=true",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// multiple documents are not allowed when artifacts != "any"
|
||||||
|
configs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "binary",
|
||||||
|
Documents: []string{
|
||||||
|
"doc1",
|
||||||
|
"doc2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
err: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(fmt.Sprintf("artifact=%q", test.configs[0].Artifacts), func(t *testing.T) {
|
||||||
|
ctx := &context.Context{
|
||||||
|
Config: config.Project{
|
||||||
|
SBOMs: test.configs,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
err := Pipe{}.Default(ctx)
|
||||||
|
if test.err {
|
||||||
|
require.Error(t, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ctx.Config.SBOMs[0].Cmd, test.cmd)
|
||||||
|
require.Equal(t, ctx.Config.SBOMs[0].Documents, test.sboms)
|
||||||
|
require.Equal(t, ctx.Config.SBOMs[0].Args, test.args)
|
||||||
|
require.Equal(t, ctx.Config.SBOMs[0].Env, test.env)
|
||||||
|
require.Equal(t, ctx.Config.SBOMs[0].Artifacts, test.artifact)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSBOMCatalogInvalidArtifacts(t *testing.T) {
|
||||||
|
ctx := context.New(config.Project{})
|
||||||
|
ctx.Config.SBOMs = []config.SBOM{
|
||||||
|
{Artifacts: "foo"},
|
||||||
|
}
|
||||||
|
err := Pipe{}.Run(ctx)
|
||||||
|
require.EqualError(t, err, "invalid list of artifacts to catalog: foo")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSeveralSBOMsWithTheSameID(t *testing.T) {
|
||||||
|
ctx := &context.Context{
|
||||||
|
Config: config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "a",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
require.EqualError(t, Pipe{}.Default(ctx), "found 2 sboms with the ID 'a', please fix your config")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSkipCataloging(t *testing.T) {
|
||||||
|
t.Run("skip", func(t *testing.T) {
|
||||||
|
require.True(t, Pipe{}.Skip(context.New(config.Project{})))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("skip SBOM cataloging", func(t *testing.T) {
|
||||||
|
ctx := context.New(config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx.SkipSBOMCataloging = true
|
||||||
|
require.True(t, Pipe{}.Skip(ctx))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("dont skip", func(t *testing.T) {
|
||||||
|
ctx := context.New(config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "all",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.False(t, Pipe{}.Skip(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSBOMCatalogArtifacts(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
desc string
|
||||||
|
ctx *context.Context
|
||||||
|
sbomPaths []string
|
||||||
|
sbomNames []string
|
||||||
|
expectedErrMsg string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
desc: "catalog errors",
|
||||||
|
expectedErrMsg: "cataloging artifacts: exit failed",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "binary",
|
||||||
|
Cmd: "exit",
|
||||||
|
Args: []string{"1"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "invalid args template",
|
||||||
|
expectedErrMsg: `cataloging artifacts failed: ${FOO}-{{ .foo }{{}}{: invalid template: template: tmpl:1: unexpected "}" in operand`,
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "binary",
|
||||||
|
Cmd: "exit",
|
||||||
|
Args: []string{"${FOO}-{{ .foo }{{}}{"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Env: []string{
|
||||||
|
"FOO=BAR",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "catalog source archives",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{Artifacts: "source"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{"artifact5.tar.gz.sbom"},
|
||||||
|
sbomNames: []string{"artifact5.tar.gz.sbom"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "catalog archives",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{Artifacts: "archive"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{"artifact1.sbom", "artifact2.sbom"},
|
||||||
|
sbomNames: []string{"artifact1.sbom", "artifact2.sbom"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "catalog linux packages",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{Artifacts: "package"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{"package1.deb.sbom"},
|
||||||
|
sbomNames: []string{"package1.deb.sbom"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "catalog binaries",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{Artifacts: "binary"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{
|
||||||
|
"artifact3-name_1.2.2_linux_amd64.sbom",
|
||||||
|
"artifact4-name_1.2.2_linux_amd64.sbom",
|
||||||
|
},
|
||||||
|
sbomNames: []string{
|
||||||
|
"artifact3-name_1.2.2_linux_amd64.sbom",
|
||||||
|
"artifact4-name_1.2.2_linux_amd64.sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "manual cataloging",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "any",
|
||||||
|
Args: []string{
|
||||||
|
"--file",
|
||||||
|
"$document0",
|
||||||
|
"--output",
|
||||||
|
"spdx-json",
|
||||||
|
"artifact5.tar.gz",
|
||||||
|
},
|
||||||
|
Documents: []string{
|
||||||
|
"final.sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{"final.sbom"},
|
||||||
|
sbomNames: []string{"final.sbom"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "multiple SBOM configs",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
Env: []string{
|
||||||
|
"SBOM_SUFFIX=s2-ish",
|
||||||
|
},
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
ID: "s1",
|
||||||
|
Artifacts: "binary",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ID: "s2",
|
||||||
|
Artifacts: "archive",
|
||||||
|
Documents: []string{"{{ .ArtifactName }}.{{ .Env.SBOM_SUFFIX }}.sbom"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{
|
||||||
|
"artifact1.s2-ish.sbom",
|
||||||
|
"artifact2.s2-ish.sbom",
|
||||||
|
"artifact3-name_1.2.2_linux_amd64.sbom",
|
||||||
|
"artifact4-name_1.2.2_linux_amd64.sbom",
|
||||||
|
},
|
||||||
|
sbomNames: []string{
|
||||||
|
"artifact1.s2-ish.sbom",
|
||||||
|
"artifact2.s2-ish.sbom",
|
||||||
|
"artifact3-name_1.2.2_linux_amd64.sbom",
|
||||||
|
"artifact4-name_1.2.2_linux_amd64.sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "catalog artifacts with filtered by ID",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "binary",
|
||||||
|
IDs: []string{"foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{
|
||||||
|
"artifact3-name_1.2.2_linux_amd64.sbom",
|
||||||
|
},
|
||||||
|
sbomNames: []string{
|
||||||
|
"artifact3-name_1.2.2_linux_amd64.sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "catalog binary artifacts with env in arguments",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "binary",
|
||||||
|
Args: []string{
|
||||||
|
"--file",
|
||||||
|
"$document",
|
||||||
|
"--output",
|
||||||
|
"spdx-json",
|
||||||
|
"$artifact",
|
||||||
|
},
|
||||||
|
Documents: []string{
|
||||||
|
"{{ .ArtifactName }}.{{ .Env.TEST_USER }}.sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Env: []string{
|
||||||
|
"TEST_USER=test-user-name",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
sbomPaths: []string{
|
||||||
|
"artifact3-name.test-user-name.sbom",
|
||||||
|
"artifact4.test-user-name.sbom",
|
||||||
|
},
|
||||||
|
sbomNames: []string{
|
||||||
|
"artifact3-name.test-user-name.sbom",
|
||||||
|
"artifact4.test-user-name.sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
desc: "cataloging 'any' artifacts fails",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
SBOMs: []config.SBOM{
|
||||||
|
{
|
||||||
|
Artifacts: "any",
|
||||||
|
Cmd: "false",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
expectedErrMsg: "cataloging artifacts: false failed: exit status 1: ",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.desc, func(t *testing.T) {
|
||||||
|
testSBOMCataloging(t, test.ctx, test.sbomPaths, test.sbomNames, test.expectedErrMsg)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSBOMCataloging(tb testing.TB, ctx *context.Context, sbomPaths, sbomNames []string, expectedErrMsg string) {
|
||||||
|
tb.Helper()
|
||||||
|
tmpdir := tb.TempDir()
|
||||||
|
|
||||||
|
ctx.Config.Dist = tmpdir
|
||||||
|
ctx.Version = "1.2.2"
|
||||||
|
|
||||||
|
// create some fake artifacts
|
||||||
|
artifacts := []string{"artifact1", "artifact2", "artifact3", "package1.deb"}
|
||||||
|
require.NoError(tb, os.Mkdir(filepath.Join(tmpdir, "linux_amd64"), os.ModePerm))
|
||||||
|
for _, f := range artifacts {
|
||||||
|
file := filepath.Join(tmpdir, f)
|
||||||
|
require.NoError(tb, os.WriteFile(file, []byte("foo"), 0o644))
|
||||||
|
}
|
||||||
|
require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "linux_amd64", "artifact4"), []byte("foo"), 0o644))
|
||||||
|
artifacts = append(artifacts, "linux_amd64/artifact4")
|
||||||
|
require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz"), []byte("foo"), 0o644))
|
||||||
|
artifacts = append(artifacts, "artifact5.tar.gz")
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "artifact1",
|
||||||
|
Path: filepath.Join(tmpdir, "artifact1"),
|
||||||
|
Type: artifact.UploadableArchive,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
artifact.ExtraID: "foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "artifact2",
|
||||||
|
Path: filepath.Join(tmpdir, "artifact2"),
|
||||||
|
Type: artifact.UploadableArchive,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
artifact.ExtraID: "foo3",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "artifact3-name",
|
||||||
|
Path: filepath.Join(tmpdir, "artifact3"),
|
||||||
|
Goos: "linux",
|
||||||
|
Goarch: "amd64",
|
||||||
|
Type: artifact.UploadableBinary,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
artifact.ExtraID: "foo",
|
||||||
|
artifact.ExtraBinary: "artifact3-name",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "artifact4",
|
||||||
|
Path: filepath.Join(tmpdir, "linux_amd64", "artifact4"),
|
||||||
|
Goos: "linux",
|
||||||
|
Goarch: "amd64",
|
||||||
|
Type: artifact.Binary,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
artifact.ExtraID: "foo3",
|
||||||
|
artifact.ExtraBinary: "artifact4-name",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "artifact5.tar.gz",
|
||||||
|
Path: filepath.Join(tmpdir, "artifact5.tar.gz"),
|
||||||
|
Type: artifact.UploadableSourceArchive,
|
||||||
|
})
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "package1.deb",
|
||||||
|
Path: filepath.Join(tmpdir, "package1.deb"),
|
||||||
|
Type: artifact.LinuxPackage,
|
||||||
|
Extra: map[string]interface{}{
|
||||||
|
artifact.ExtraID: "foo",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
// configure the pipeline
|
||||||
|
require.NoError(tb, Pipe{}.Default(ctx))
|
||||||
|
|
||||||
|
// run the pipeline
|
||||||
|
if expectedErrMsg != "" {
|
||||||
|
err := Pipe{}.Run(ctx)
|
||||||
|
require.Error(tb, err)
|
||||||
|
require.Contains(tb, err.Error(), expectedErrMsg)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
require.NoError(tb, Pipe{}.Run(ctx))
|
||||||
|
|
||||||
|
// ensure all artifacts have an ID
|
||||||
|
for _, arti := range ctx.Artifacts.Filter(artifact.ByType(artifact.SBOM)).List() {
|
||||||
|
require.NotEmptyf(tb, arti.ID(), ".Extra.ID on %s", arti.Path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// verify that only the artifacts and the sboms are in the dist dir
|
||||||
|
gotFiles := []string{}
|
||||||
|
|
||||||
|
require.NoError(tb, filepath.Walk(tmpdir,
|
||||||
|
func(path string, info os.FileInfo, err error) error {
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if info.IsDir() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
relPath, err := filepath.Rel(tmpdir, path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
gotFiles = append(gotFiles, relPath)
|
||||||
|
return nil
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
|
||||||
|
wantFiles := append(artifacts, sbomPaths...)
|
||||||
|
sort.Strings(wantFiles)
|
||||||
|
require.ElementsMatch(tb, wantFiles, gotFiles, "SBOM paths differ")
|
||||||
|
|
||||||
|
var sbomArtifacts []string
|
||||||
|
for _, sig := range ctx.Artifacts.Filter(artifact.ByType(artifact.SBOM)).List() {
|
||||||
|
sbomArtifacts = append(sbomArtifacts, sig.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
require.ElementsMatch(tb, sbomArtifacts, sbomNames, "SBOM names differ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_subprocessDistPath(t *testing.T) {
|
||||||
|
cwd, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
distDir string
|
||||||
|
pathRelativeToCwd string
|
||||||
|
expects string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "relative dist with anchor",
|
||||||
|
distDir: "./dist",
|
||||||
|
pathRelativeToCwd: "dist/my.sbom",
|
||||||
|
expects: "my.sbom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "relative dist without anchor",
|
||||||
|
distDir: "dist",
|
||||||
|
pathRelativeToCwd: "dist/my.sbom",
|
||||||
|
expects: "my.sbom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "relative dist with nested resource",
|
||||||
|
distDir: "dist",
|
||||||
|
pathRelativeToCwd: "dist/something/my.sbom",
|
||||||
|
expects: "something/my.sbom",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "absolute dist with nested resource",
|
||||||
|
distDir: filepath.Join(cwd, "dist/"),
|
||||||
|
pathRelativeToCwd: "dist/something/my.sbom",
|
||||||
|
expects: "something/my.sbom",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
actual, err := subprocessDistPath(test.distDir, test.pathRelativeToCwd)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, test.expects, actual)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
@@ -76,11 +76,14 @@ func (Pipe) Run(ctx *context.Context) error {
|
|||||||
artifact.ByType(artifact.UploadableSourceArchive),
|
artifact.ByType(artifact.UploadableSourceArchive),
|
||||||
artifact.ByType(artifact.Checksum),
|
artifact.ByType(artifact.Checksum),
|
||||||
artifact.ByType(artifact.LinuxPackage),
|
artifact.ByType(artifact.LinuxPackage),
|
||||||
|
artifact.ByType(artifact.SBOM),
|
||||||
))
|
))
|
||||||
case "archive":
|
case "archive":
|
||||||
filters = append(filters, artifact.ByType(artifact.UploadableArchive))
|
filters = append(filters, artifact.ByType(artifact.UploadableArchive))
|
||||||
case "binary":
|
case "binary":
|
||||||
filters = append(filters, artifact.ByType(artifact.UploadableBinary))
|
filters = append(filters, artifact.ByType(artifact.UploadableBinary))
|
||||||
|
case "sbom":
|
||||||
|
filters = append(filters, artifact.ByType(artifact.SBOM))
|
||||||
case "package":
|
case "package":
|
||||||
filters = append(filters, artifact.ByType(artifact.LinuxPackage))
|
filters = append(filters, artifact.ByType(artifact.LinuxPackage))
|
||||||
case "none": // TODO(caarlos0): this is not very useful, lets remove it.
|
case "none": // TODO(caarlos0): this is not very useful, lets remove it.
|
||||||
@@ -200,7 +203,7 @@ func signone(ctx *context.Context, cfg config.Sign, art *artifact.Artifact) ([]*
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// re-execute template results, using artifact name as artifact so they eval to the actual needed file name.
|
// re-execute template results, using artifact desc as artifact so they eval to the actual needed file desc.
|
||||||
env["artifact"] = art.Name
|
env["artifact"] = art.Name
|
||||||
name, _ = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env)) // could never error as it passed the previous check
|
name, _ = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Signature, env)) // could never error as it passed the previous check
|
||||||
cert, _ = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Certificate, env)) // could never error as it passed the previous check
|
cert, _ = tmpl.New(ctx).WithEnv(env).Apply(expand(cfg.Certificate, env)) // could never error as it passed the previous check
|
||||||
|
@@ -12,6 +12,8 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/goreleaser/goreleaser/internal/artifact"
|
"github.com/goreleaser/goreleaser/internal/artifact"
|
||||||
"github.com/goreleaser/goreleaser/pkg/config"
|
"github.com/goreleaser/goreleaser/pkg/config"
|
||||||
"github.com/goreleaser/goreleaser/pkg/context"
|
"github.com/goreleaser/goreleaser/pkg/context"
|
||||||
@@ -183,18 +185,6 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
desc: "sign single",
|
|
||||||
ctx: context.New(
|
|
||||||
config.Project{
|
|
||||||
Signs: []config.Sign{
|
|
||||||
{Artifacts: "all"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
),
|
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
desc: "sign all artifacts",
|
desc: "sign all artifacts",
|
||||||
ctx: context.New(
|
ctx: context.New(
|
||||||
@@ -206,8 +196,8 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sign archives",
|
desc: "sign archives",
|
||||||
@@ -357,6 +347,20 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
signaturePaths: []string{"artifact5.tar.gz.sig"},
|
signaturePaths: []string{"artifact5.tar.gz.sig"},
|
||||||
signatureNames: []string{"artifact5.tar.gz.sig"},
|
signatureNames: []string{"artifact5.tar.gz.sig"},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
desc: "sign only sbom",
|
||||||
|
ctx: context.New(
|
||||||
|
config.Project{
|
||||||
|
Signs: []config.Sign{
|
||||||
|
{
|
||||||
|
Artifacts: "sbom",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
signaturePaths: []string{"artifact5.tar.gz.sbom.sig"},
|
||||||
|
signatureNames: []string{"artifact5.tar.gz.sbom.sig"},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
desc: "sign all artifacts with env",
|
desc: "sign all artifacts with env",
|
||||||
ctx: context.New(
|
ctx: context.New(
|
||||||
@@ -379,8 +383,8 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sign all artifacts with template",
|
desc: "sign all artifacts with template",
|
||||||
@@ -404,8 +408,8 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
desc: "sign single with password from stdin",
|
desc: "sign single with password from stdin",
|
||||||
@@ -432,8 +436,8 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
user: passwordUser,
|
user: passwordUser,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -462,8 +466,8 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
user: passwordUser,
|
user: passwordUser,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -491,8 +495,8 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
user: passwordUser,
|
user: passwordUser,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -545,9 +549,9 @@ func TestSignArtifacts(t *testing.T) {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signaturePaths: []string{"artifact1.sig", "artifact2.sig", "artifact3.sig", "checksum.sig", "checksum2.sig", "linux_amd64/artifact4.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "package1.deb.sig"},
|
signatureNames: []string{"artifact1.sig", "artifact2.sig", "artifact3_1.0.0_linux_amd64.sig", "checksum.sig", "checksum2.sig", "artifact4_1.0.0_linux_amd64.sig", "artifact5.tar.gz.sig", "artifact5.tar.gz.sbom.sig", "package1.deb.sig"},
|
||||||
certificateNames: []string{"artifact1_honk.pem", "artifact2_honk.pem", "artifact3_1.0.0_linux_amd64_honk.pem", "checksum_honk.pem", "checksum2_honk.pem", "artifact4_1.0.0_linux_amd64_honk.pem", "artifact5_honk.pem", "package1_honk.pem"},
|
certificateNames: []string{"artifact1_honk.pem", "artifact2_honk.pem", "artifact3_1.0.0_linux_amd64_honk.pem", "checksum_honk.pem", "checksum2_honk.pem", "artifact4_1.0.0_linux_amd64_honk.pem", "artifact5_honk.pem", "artifact5.tar.gz.sbom_honk.pem", "package1_honk.pem"},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -579,6 +583,8 @@ func testSign(tb testing.TB, ctx *context.Context, certificateNames, signaturePa
|
|||||||
artifacts = append(artifacts, "linux_amd64/artifact4")
|
artifacts = append(artifacts, "linux_amd64/artifact4")
|
||||||
require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz"), []byte("foo"), 0o644))
|
require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz"), []byte("foo"), 0o644))
|
||||||
artifacts = append(artifacts, "artifact5.tar.gz")
|
artifacts = append(artifacts, "artifact5.tar.gz")
|
||||||
|
require.NoError(tb, os.WriteFile(filepath.Join(tmpdir, "artifact5.tar.gz.sbom"), []byte("sbom(foo)"), 0o644))
|
||||||
|
artifacts = append(artifacts, "artifact5.tar.gz.sbom")
|
||||||
ctx.Artifacts.Add(&artifact.Artifact{
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
Name: "artifact1",
|
Name: "artifact1",
|
||||||
Path: filepath.Join(tmpdir, "artifact1"),
|
Path: filepath.Join(tmpdir, "artifact1"),
|
||||||
@@ -632,6 +638,11 @@ func testSign(tb testing.TB, ctx *context.Context, certificateNames, signaturePa
|
|||||||
Path: filepath.Join(tmpdir, "artifact5.tar.gz"),
|
Path: filepath.Join(tmpdir, "artifact5.tar.gz"),
|
||||||
Type: artifact.UploadableSourceArchive,
|
Type: artifact.UploadableSourceArchive,
|
||||||
})
|
})
|
||||||
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
|
Name: "artifact5.tar.gz.sbom",
|
||||||
|
Path: filepath.Join(tmpdir, "artifact5.tar.gz.sbom"),
|
||||||
|
Type: artifact.SBOM,
|
||||||
|
})
|
||||||
ctx.Artifacts.Add(&artifact.Artifact{
|
ctx.Artifacts.Add(&artifact.Artifact{
|
||||||
Name: "package1.deb",
|
Name: "package1.deb",
|
||||||
Path: filepath.Join(tmpdir, "package1.deb"),
|
Path: filepath.Join(tmpdir, "package1.deb"),
|
||||||
@@ -677,12 +688,8 @@ func testSign(tb testing.TB, ctx *context.Context, certificateNames, signaturePa
|
|||||||
certNames = append(certNames, cert.Name)
|
certNames = append(certNames, cert.Name)
|
||||||
require.True(tb, strings.HasPrefix(cert.Path, ctx.Config.Dist))
|
require.True(tb, strings.HasPrefix(cert.Path, ctx.Config.Dist))
|
||||||
}
|
}
|
||||||
sort.Strings(certificateNames)
|
|
||||||
sort.Strings(certNames)
|
assert.ElementsMatch(tb, certificateNames, certNames)
|
||||||
require.Equal(tb, len(certificateNames), len(certificates))
|
|
||||||
if len(certificateNames) > 0 {
|
|
||||||
require.Equal(tb, certificateNames, certNames)
|
|
||||||
}
|
|
||||||
|
|
||||||
// verify that only the artifacts and the signatures are in the dist dir
|
// verify that only the artifacts and the signatures are in the dist dir
|
||||||
gotFiles := []string{}
|
gotFiles := []string{}
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/goreleaser/goreleaser/internal/pipe/krew"
|
"github.com/goreleaser/goreleaser/internal/pipe/krew"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/nfpm"
|
"github.com/goreleaser/goreleaser/internal/pipe/nfpm"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/publish"
|
"github.com/goreleaser/goreleaser/internal/pipe/publish"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/pipe/sbom"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/scoop"
|
"github.com/goreleaser/goreleaser/internal/pipe/scoop"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/semver"
|
"github.com/goreleaser/goreleaser/internal/pipe/semver"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/sign"
|
"github.com/goreleaser/goreleaser/internal/pipe/sign"
|
||||||
@@ -75,6 +76,7 @@ var Pipeline = append(
|
|||||||
gofish.Pipe{}, // create gofish rig
|
gofish.Pipe{}, // create gofish rig
|
||||||
krew.Pipe{}, // krew plugins
|
krew.Pipe{}, // krew plugins
|
||||||
scoop.Pipe{}, // create scoop buckets
|
scoop.Pipe{}, // create scoop buckets
|
||||||
|
sbom.Pipe{}, // create SBOMs of artifacts
|
||||||
checksums.Pipe{}, // checksums of the files
|
checksums.Pipe{}, // checksums of the files
|
||||||
sign.Pipe{}, // sign artifacts
|
sign.Pipe{}, // sign artifacts
|
||||||
docker.Pipe{}, // create and push docker images
|
docker.Pipe{}, // create and push docker images
|
||||||
|
@@ -619,6 +619,17 @@ type NFPMOverridables struct {
|
|||||||
APK NFPMAPK `yaml:"apk,omitempty"`
|
APK NFPMAPK `yaml:"apk,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SBOM config.
|
||||||
|
type SBOM struct {
|
||||||
|
ID string `yaml:"id,omitempty"`
|
||||||
|
Cmd string `yaml:"cmd,omitempty"`
|
||||||
|
Env []string `yaml:"env,omitempty"`
|
||||||
|
Args []string `yaml:"args,omitempty"`
|
||||||
|
Documents []string `yaml:"documents,omitempty"`
|
||||||
|
Artifacts string `yaml:"artifacts,omitempty"`
|
||||||
|
IDs []string `yaml:"ids,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
// Sign config.
|
// Sign config.
|
||||||
type Sign struct {
|
type Sign struct {
|
||||||
ID string `yaml:"id,omitempty"`
|
ID string `yaml:"id,omitempty"`
|
||||||
@@ -836,6 +847,7 @@ type Project struct {
|
|||||||
Source Source `yaml:"source,omitempty"`
|
Source Source `yaml:"source,omitempty"`
|
||||||
GoMod GoMod `yaml:"gomod,omitempty"`
|
GoMod GoMod `yaml:"gomod,omitempty"`
|
||||||
Announce Announce `yaml:"announce,omitempty"`
|
Announce Announce `yaml:"announce,omitempty"`
|
||||||
|
SBOMs []SBOM `yaml:"sboms,omitempty"`
|
||||||
|
|
||||||
UniversalBinaries []UniversalBinary `yaml:"universal_binaries,omitempty"`
|
UniversalBinaries []UniversalBinary `yaml:"universal_binaries,omitempty"`
|
||||||
|
|
||||||
|
@@ -92,6 +92,7 @@ type Context struct {
|
|||||||
SkipAnnounce bool
|
SkipAnnounce bool
|
||||||
SkipSign bool
|
SkipSign bool
|
||||||
SkipValidate bool
|
SkipValidate bool
|
||||||
|
SkipSBOMCataloging bool
|
||||||
RmDist bool
|
RmDist bool
|
||||||
PreRelease bool
|
PreRelease bool
|
||||||
Deprecated bool
|
Deprecated bool
|
||||||
|
@@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/goreleaser/goreleaser/internal/pipe/project"
|
"github.com/goreleaser/goreleaser/internal/pipe/project"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/reddit"
|
"github.com/goreleaser/goreleaser/internal/pipe/reddit"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/release"
|
"github.com/goreleaser/goreleaser/internal/pipe/release"
|
||||||
|
"github.com/goreleaser/goreleaser/internal/pipe/sbom"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/scoop"
|
"github.com/goreleaser/goreleaser/internal/pipe/scoop"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/sign"
|
"github.com/goreleaser/goreleaser/internal/pipe/sign"
|
||||||
"github.com/goreleaser/goreleaser/internal/pipe/slack"
|
"github.com/goreleaser/goreleaser/internal/pipe/slack"
|
||||||
@@ -62,6 +63,7 @@ var Defaulters = []Defaulter{
|
|||||||
checksums.Pipe{},
|
checksums.Pipe{},
|
||||||
sign.Pipe{},
|
sign.Pipe{},
|
||||||
sign.DockerPipe{},
|
sign.DockerPipe{},
|
||||||
|
sbom.Pipe{},
|
||||||
docker.Pipe{},
|
docker.Pipe{},
|
||||||
docker.ManifestPipe{},
|
docker.ManifestPipe{},
|
||||||
artifactory.Pipe{},
|
artifactory.Pipe{},
|
||||||
|
102
www/docs/customization/sbom.md
Normal file
102
www/docs/customization/sbom.md
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
# Cataloging artifacts
|
||||||
|
|
||||||
|
A Software Bill of Materials (SBOM) is a description of the components that make up a software artifact.
|
||||||
|
|
||||||
|
Goreleaser can create one or more SBOMs for any artifacts generated by Goreleaser.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
The artifact cataloging step can analyze one or more artifacts generated by Goreleaser and output one or more
|
||||||
|
SBOM files into the dist directory.
|
||||||
|
|
||||||
|
The default is configured to create an SBOM for each binary produced with [Syft](https://github.com/anchore/syft).
|
||||||
|
To enable artifact cataloging just add:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .goreleaser.yml
|
||||||
|
sboms:
|
||||||
|
- artifacts: archive
|
||||||
|
```
|
||||||
|
|
||||||
|
To customize the artifact cataloging pipeline you can use the following options:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# .goreleaser.yml
|
||||||
|
sboms:
|
||||||
|
-
|
||||||
|
# ID of the sbom config, must be unique.
|
||||||
|
#
|
||||||
|
# Defaults to "default".
|
||||||
|
id: foo
|
||||||
|
|
||||||
|
# List of Names/templates of the SBOM documents created at this step (relative to the dist dir).
|
||||||
|
#
|
||||||
|
# Each element configured is made available as variables. For example:
|
||||||
|
# documents: ["foo", "bar"]
|
||||||
|
#
|
||||||
|
# would make the following variables that can be referenced as template keys:
|
||||||
|
# document0: "foo"
|
||||||
|
# document1: "bar"
|
||||||
|
#
|
||||||
|
# Default value is conditional based on the value of "artifacts"
|
||||||
|
# - "binary": ["{{ .Binary }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}.sbom"]
|
||||||
|
# - "any": []
|
||||||
|
# - otherwise: ["{{ .ArtifactName }}.sbom"]
|
||||||
|
#
|
||||||
|
# Note that multiple sbom values are only allowed if the value of "artifacts" is "any".
|
||||||
|
documents:
|
||||||
|
- "${artifact}.spdx.sbom"
|
||||||
|
|
||||||
|
# Path to the SBOM generator command
|
||||||
|
#
|
||||||
|
# Note: the process CWD will be set to the same location as "dist"
|
||||||
|
#
|
||||||
|
# Defaults to `syft`
|
||||||
|
cmd: syft
|
||||||
|
|
||||||
|
# Command line templateable arguments for the command
|
||||||
|
#
|
||||||
|
# Defaults to `["$artifact", "--file", "$document", "--output", "spdx-json"]`
|
||||||
|
args: ["$artifact", "--file", "$sbom", "--output", "spdx-json"]
|
||||||
|
|
||||||
|
# List of environment variables that will be passed to the SBOM command as well as the templates.
|
||||||
|
#
|
||||||
|
# Defaults to [ "SYFT_FILE_METADATA_CATALOGER_ENABLED=true" ]
|
||||||
|
env:
|
||||||
|
- FOO=bar
|
||||||
|
- HONK=honkhonk
|
||||||
|
|
||||||
|
# Which artifacts to catalog
|
||||||
|
#
|
||||||
|
# any: let the SBOM tool decide what artifacts available in the cwd should be cataloged
|
||||||
|
# source: source archive
|
||||||
|
# package: linux packages (deb, rpm, apk)
|
||||||
|
# archive: archives from archive pipe
|
||||||
|
# binary: binaries output from the build stage
|
||||||
|
#
|
||||||
|
# Defaults to `archive`
|
||||||
|
artifacts: archive
|
||||||
|
|
||||||
|
# IDs of the artifacts to catalog.
|
||||||
|
#
|
||||||
|
# If `artifacts` is "source" or "any" then this fields has no effect.
|
||||||
|
#
|
||||||
|
# Defaults to empty (which implies no filtering).
|
||||||
|
ids:
|
||||||
|
- foo
|
||||||
|
- bar
|
||||||
|
```
|
||||||
|
|
||||||
|
### Available variable names
|
||||||
|
|
||||||
|
These environment variables might be available in the fields that are templateable:
|
||||||
|
|
||||||
|
- `${artifact}`: the path to the artifact that will be cataloged (unless "artifacts" config item is "any")
|
||||||
|
- `${artifactID}`: the ID of the artifact that will be cataloged (unless "artifacts" config item is "any")
|
||||||
|
- `${document}`: the SBOM filename generated (corresponds to `${document0}` if the "artifacts" config item is "any")
|
||||||
|
- `${document#}`: the SBOM filenames generated, where `#` corresponds to the list index under the "documents" config item (e.g. `${document0}`)
|
||||||
|
|
||||||
|
## Limitations
|
||||||
|
|
||||||
|
Container images generated by Goreleaser are not available to be cataloged by the SBOM tool.
|
||||||
|
|
@@ -60,6 +60,7 @@ signs:
|
|||||||
# package: linux packages (deb, rpm, apk)
|
# package: linux packages (deb, rpm, apk)
|
||||||
# archive: archives from archive pipe
|
# archive: archives from archive pipe
|
||||||
# binary: binaries if archiving format is set to binary
|
# binary: binaries if archiving format is set to binary
|
||||||
|
# sbom: any Software Bill of Materials generated for other artifacts
|
||||||
#
|
#
|
||||||
# Defaults to `none`
|
# Defaults to `none`
|
||||||
artifacts: all
|
artifacts: all
|
||||||
|
@@ -91,6 +91,7 @@ nav:
|
|||||||
- customization/snapcraft.md
|
- customization/snapcraft.md
|
||||||
- customization/docker.md
|
- customization/docker.md
|
||||||
- customization/docker_manifest.md
|
- customization/docker_manifest.md
|
||||||
|
- customization/sbom.md
|
||||||
- Signing:
|
- Signing:
|
||||||
- Checksums and artifacts: customization/sign.md
|
- Checksums and artifacts: customization/sign.md
|
||||||
- Docker Images and Manifests: customization/docker_sign.md
|
- Docker Images and Manifests: customization/docker_sign.md
|
||||||
|
Reference in New Issue
Block a user