Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions docs/_data/werf_yaml.yml
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,33 @@ sections:
all: "/usage/build/stapel/imports.html"
- <<: *dockerfile-image-section-final
- <<: *image-section-sbom
- name: packages
description:
en: "Set of directives to catalog language package manifests into the SBOM"
ru: "Набор директив для каталогизации манифестов пакетов языков в SBOM"
collapsible: true
isCollapsedByDefault: false
directiveList:
- name: type
value: "string"
description:
en: "Package ecosystem type (go-mod)"
ru: "Тип экосистемы пакетов (go-mod)"
- name: workdir
value: "string"
description:
en: "Directory inside the image that contains the module files (required for go-mod)"
ru: "Директория внутри образа, содержащая файлы модуля (обязательно для go-mod)"
- name: spec
value: "string"
description:
en: "Module file name (for go-mod, default: go.mod)"
ru: "Имя файла модуля (для go-mod, по умолчанию: go.mod)"
- name: lock
value: "string"
description:
en: "Checksum file name (for go-mod, default: go.sum)"
ru: "Имя файла контрольных сумм (для go-mod, по умолчанию: go.sum)"
- <<: *meta-section-build-cache-version
- name: platform
description:
Expand Down
21 changes: 20 additions & 1 deletion pkg/build/build_phase.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/werf/werf/v2/pkg/sbom/externalref"
"github.com/werf/werf/v2/pkg/sbom/gomod"
sbomImage "github.com/werf/werf/v2/pkg/sbom/image"
"github.com/werf/werf/v2/pkg/sbom/managedinput"
"github.com/werf/werf/v2/pkg/sbom/scanner"
"github.com/werf/werf/v2/pkg/stapel"
"github.com/werf/werf/v2/pkg/storage"
Expand Down Expand Up @@ -336,13 +337,31 @@ func (phase *BuildPhase) convergeImageSbom(ctx context.Context, name string, ima
goModPatcher,
}

if err := phase.sbomStep.ConvergeWithMerge(ctx, name, stageDesc, scanner.DefaultSyftScanOptions(), mergeOpts, patchers, primaryImg.TargetPlatform); err != nil {
scanOpts := phase.scanOptionsForImage(primaryImg)

if err := phase.sbomStep.ConvergeWithMerge(ctx, name, stageDesc, scanOpts, mergeOpts, patchers, primaryImg.TargetPlatform); err != nil {
return fmt.Errorf("unable to converge sbom for image %q: %w", name, err)
}

return nil
}

func (phase *BuildPhase) scanOptionsForImage(img *image.Image) scanner.ScanOptions {
scanOpts := scanner.DefaultSyftScanOptions()

stapelConfig := img.StapelImageConfig
if stapelConfig == nil {
return scanOpts
}

catalogers := managedinput.ToCatalogers(stapelConfig.ImageBaseConfig().Packages)
for i := range scanOpts.Commands {
scanOpts.Commands[i].Catalogers = catalogers
}

return scanOpts
}

func (phase *BuildPhase) targetPlatforms(ctx context.Context, forcedTargetPlatforms, commonTargetPlatforms []string, name string, images []*image.Image) ([]string, error) {
// TODO: this target platforms assertion could be removed in future versions and now exists only as a additional self-testing code
var targetPlatforms []string
Expand Down
3 changes: 3 additions & 0 deletions pkg/build/sbom_step.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
"github.com/werf/werf/v2/pkg/sbom/cyclonedxutil"
"github.com/werf/werf/v2/pkg/sbom/cyclonedxutil/gost"
sbomImage "github.com/werf/werf/v2/pkg/sbom/image"
"github.com/werf/werf/v2/pkg/sbom/managedinput"
"github.com/werf/werf/v2/pkg/sbom/scanner"
"github.com/werf/werf/v2/pkg/storage"
)
Expand Down Expand Up @@ -82,6 +83,8 @@ func (step *sbomStep) ConvergeWithMerge(ctx context.Context, werfImgName string,
return fmt.Errorf("parse scanned BOM: %w", err)
}

managedinput.FilterBOMBySourcePaths(targetBOM, scanOpts.Commands[0].Catalogers)

if err := gost.Upsert(targetBOM, mergeOpts.Gost); err != nil {
return fmt.Errorf("set GOST properties: %w", err)
}
Expand Down
40 changes: 28 additions & 12 deletions pkg/config/packages_directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,34 +2,50 @@ package config

import "fmt"

// PackagesDirectiveType enumerates supported package source types.
type PackagesDirectiveType string

const (
PackagesDirectiveTypeOSPM PackagesDirectiveType = "os-pm"
PackagesDirectiveTypeOSPM PackagesDirectiveType = "os-pm"
PackagesDirectiveTypeGoMod PackagesDirectiveType = "go-mod"
)

const (
goModDefaultSpec = "go.mod"
goModDefaultLock = "go.sum"
)

// PackagesSpec stores a package specification which is either a file path
// (string) or an inline package list ([]string).
type PackagesSpec struct {
FilePath string
Packages []string
}

// PackagesDirective represents a single entry in the image-level packages list.
// GoModSpec describes Go module files inside the image. Workdir is the directory
// holding the module files; Spec and Lock default to "go.mod" and "go.sum".
type GoModSpec struct {
Workdir string
Spec string
Lock string
}

type PackagesDirective struct {
Type PackagesDirectiveType
Spec PackagesSpec
Type PackagesDirectiveType
Spec PackagesSpec
GoMod GoModSpec
}

func (d *PackagesDirective) validate() error {
if d.Type != PackagesDirectiveTypeOSPM {
switch d.Type {
case PackagesDirectiveTypeOSPM:
if d.Spec.FilePath == "" && len(d.Spec.Packages) == 0 {
return fmt.Errorf("packages spec must not be empty for type %q", d.Type)
}
case PackagesDirectiveTypeGoMod:
if d.GoMod.Workdir == "" {
return fmt.Errorf("the `workdir` is required for type %q", d.Type)
}
default:
return fmt.Errorf("unsupported packages type %q", d.Type)
}

if d.Spec.FilePath == "" && len(d.Spec.Packages) == 0 {
return fmt.Errorf("packages spec must not be empty for type %q", d.Type)
}

return nil
}
143 changes: 143 additions & 0 deletions pkg/config/packages_directive_go_mod_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package config

import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"gopkg.in/yaml.v2"

"github.com/werf/common-go/pkg/util"
)

var _ = Describe("rawPackagesDirective go-mod", func() {
var localGitRepo *LocalGitRepoStub
var giterminismManager *GiterminismManagerStub

BeforeEach(func() {
parentStack = util.NewStack()
localGitRepo = NewLocalGitRepoStub("9d8059842b6fde712c58315ca0ab4713d90761c0")
giterminismManager = NewGiterminismManagerStub(localGitRepo)
})

directivesFromYaml := func(yamlMap map[string]interface{}) ([]*PackagesDirective, error) {
rawYaml, err := yaml.Marshal(yamlMap)
Expect(err).To(Succeed())

doc := &doc{Content: rawYaml}
rawStapelImage := &rawStapelImage{doc: doc}

Expect(yaml.UnmarshalStrict(doc.Content, rawStapelImage)).To(Succeed())

stapelImage, err := rawStapelImage.toStapelImageDirective(giterminismManager, &Meta{}, "image1")
if err != nil {
return nil, err
}
return stapelImage.Packages, nil
}

DescribeTable("unmarshal and convert succeed",
func(yamlMap map[string]interface{}, expected []*PackagesDirective) {
packages, err := directivesFromYaml(yamlMap)
Expect(err).To(Succeed())

Expect(packages).To(HaveLen(len(expected)))
for i, exp := range expected {
Expect(packages[i].Type).To(Equal(exp.Type))
Expect(packages[i].GoMod).To(Equal(exp.GoMod))
}
},

Entry("go-mod with only workdir defaults spec and lock",
map[string]interface{}{
"image": "image1",
"from": "golang:1.23-alpine",
"packages": []map[string]interface{}{
{
"type": "go-mod",
"workdir": "/app/api",
},
},
},
[]*PackagesDirective{
{
Type: PackagesDirectiveTypeGoMod,
GoMod: GoModSpec{
Workdir: "/app/api",
Spec: "go.mod",
Lock: "go.sum",
},
},
},
),

Entry("go-mod with explicit spec and lock",
map[string]interface{}{
"image": "image1",
"from": "golang:1.23-alpine",
"packages": []map[string]interface{}{
{
"type": "go-mod",
"workdir": "/app/cli",
"spec": "go.mod",
"lock": "go.sum",
},
},
},
[]*PackagesDirective{
{
Type: PackagesDirectiveTypeGoMod,
GoMod: GoModSpec{
Workdir: "/app/cli",
Spec: "go.mod",
Lock: "go.sum",
},
},
},
),

Entry("multiple go-mod entries",
map[string]interface{}{
"image": "image1",
"from": "golang:1.23-alpine",
"packages": []map[string]interface{}{
{
"type": "go-mod",
"workdir": "/app/api",
},
{
"type": "go-mod",
"workdir": "/app/cli",
},
},
},
[]*PackagesDirective{
{
Type: PackagesDirectiveTypeGoMod,
GoMod: GoModSpec{Workdir: "/app/api", Spec: "go.mod", Lock: "go.sum"},
},
{
Type: PackagesDirectiveTypeGoMod,
GoMod: GoModSpec{Workdir: "/app/cli", Spec: "go.mod", Lock: "go.sum"},
},
},
),
)

DescribeTable("convert to directive fails when required fields are missing",
func(yamlMap map[string]interface{}) {
_, err := directivesFromYaml(yamlMap)
Expect(err).To(HaveOccurred())
},

Entry("go-mod without workdir",
map[string]interface{}{
"image": "image1",
"from": "golang:1.23-alpine",
"packages": []map[string]interface{}{
{
"type": "go-mod",
},
},
},
),
)
})
47 changes: 38 additions & 9 deletions pkg/config/raw_packages_directive.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
)

type rawPackagesDirective struct {
Type string `yaml:"type,omitempty"`
Spec interface{} `yaml:"spec,omitempty"`
Type string `yaml:"type,omitempty"`
Spec interface{} `yaml:"spec,omitempty"`
Workdir string `yaml:"workdir,omitempty"`
Lock string `yaml:"lock,omitempty"`

rawStapelImage *rawStapelImage `yaml:"-"`

Expand Down Expand Up @@ -34,8 +36,8 @@ func (r *rawPackagesDirective) UnmarshalYAML(unmarshal func(interface{}) error)
return newDetailedConfigError("the `type` is required for each packages directive entry!", nil, r.docForErrors())
}

if r.Spec == nil {
return newDetailedConfigError("the `spec` is required for each packages directive entry!", nil, r.docForErrors())
if PackagesDirectiveType(r.Type) == PackagesDirectiveTypeOSPM && r.Spec == nil {
return newDetailedConfigError("the `spec` is required for `os-pm` packages directive entry!", nil, r.docForErrors())
}

return nil
Expand All @@ -53,22 +55,49 @@ func (r *rawPackagesDirective) toDirective() (*PackagesDirective, error) {
Type: PackagesDirectiveType(r.Type),
}

switch d.Type {
case PackagesDirectiveTypeOSPM:
if err := r.fillOSPMSpec(d); err != nil {
return nil, err
}
case PackagesDirectiveTypeGoMod:
r.fillGoModSpec(d)
}

if err := d.validate(); err != nil {
return nil, err
}

return d, nil
}

func (r *rawPackagesDirective) fillOSPMSpec(d *PackagesDirective) error {
switch v := r.Spec.(type) {
case string:
d.Spec.FilePath = v
case []interface{}:
packages, err := InterfaceToStringArray(v, nil, r.rawStapelImage.doc)
if err != nil {
return nil, err
return err
}
d.Spec.Packages = packages
default:
return nil, fmt.Errorf("unsupported packages spec type %T for type %q", r.Spec, r.Type)
return fmt.Errorf("unsupported packages spec type %T for type %q", r.Spec, r.Type)
}

if err := d.validate(); err != nil {
return nil, err
return nil
}

func (r *rawPackagesDirective) fillGoModSpec(d *PackagesDirective) {
d.GoMod.Workdir = r.Workdir

d.GoMod.Spec = goModDefaultSpec
if spec, ok := r.Spec.(string); ok && spec != "" {
d.GoMod.Spec = spec
}

return d, nil
d.GoMod.Lock = goModDefaultLock
if r.Lock != "" {
d.GoMod.Lock = r.Lock
}
}
1 change: 1 addition & 0 deletions pkg/container_backend/docker_server_backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,7 @@ func (backend *DockerServerBackend) GenerateSBOM(ctx context.Context, scanOpts s
var bomJSON []byte
err := logboek.Context(ctx).Default().LogProcess("Scan image %q", scanOpts.Commands[0].SourcePath).DoError(func() error {
runArgs := mapSbomScanOptionsToDockerRunCommand(wt.RootDir(), wt.BillsDir(), billNames, scanOpts)
logboek.Context(ctx).Debug().LogF("docker %s\n", strings.Join(runArgs, " "))
if _, err := docker.CliRun_RecordedOutput(ctx, runArgs...); err != nil {
return fmt.Errorf("run scanner: %w", err)
}
Expand Down
Loading
Loading