diff --git a/docs/_data/werf_yaml.yml b/docs/_data/werf_yaml.yml index c91526aa73..98d238aa4a 100644 --- a/docs/_data/werf_yaml.yml +++ b/docs/_data/werf_yaml.yml @@ -93,11 +93,6 @@ sections: en: "/usage/build/process.html#scanning-and-generation-of-sbom-artifacts-experimental" ru: "/usage/build/process.html#сканирование-и-генерация-sbom-артефактов-experimental" directives: - - name: fragment - value: "string" - description: - en: "Additional SBOM data in CycloneDX format" - ru: "Дополнительные данные SBOM в формате CycloneDX" - <<: *gost-directive - name: imageSpec description: diff --git a/docs/pages_en/usage/build/process.md b/docs/pages_en/usage/build/process.md index 6fc88adfe3..3999463eb0 100644 --- a/docs/pages_en/usage/build/process.md +++ b/docs/pages_en/usage/build/process.md @@ -699,37 +699,6 @@ build: standard: cyclonedx@1.6 ``` -### Per-image configuration (`sbom.fragment`) - -Optionally you can provide additional SBOM data for each image via the `sbom.fragment` property. This can be used to manually include components that are not automatically detected by the scanner. - -`sbom.fragment` must be a YAML CycloneDX@1.6 document or a partial fragment (e.g., only the `components:` section). werf will build a full CycloneDX@1.6 BOM document by combining the scan results with this fragment. - -```yaml -project: werf-sbom-base-image-example -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: base-image -from: registry.werf.io/werf/scratch:latest -sbom: - fragment: | - components: - - type: library - name: openssl - version: "3.0.0" - purl: pkg:generic/openssl@3.0.0 - licenses: - - license: - id: Apache-2.0 - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 -``` - The scanning result will be saved as a separate image with the `-sbom` postfix in the local backend storage (for example, Docker), and will also be sent to the container registry if the `--repo` flag is specified. ### GOST security properties (`sbom.gost`) diff --git a/docs/pages_ru/usage/build/process.md b/docs/pages_ru/usage/build/process.md index 27e089a36e..b209b39a2a 100644 --- a/docs/pages_ru/usage/build/process.md +++ b/docs/pages_ru/usage/build/process.md @@ -698,37 +698,6 @@ build: standard: cyclonedx@1.6 ``` -### Конфигурация конкретного образа (`sbom.fragment`) - -Опционально можно предоставить дополнительные SBOM-данные для каждого образа с помощью свойства `sbom.fragment`. Это может быть использовано для ручного включения компонентов, которые не были автоматически обнаружены сканером. - -`sbom.fragment` должен быть YAML-документом CycloneDX@1.6 или его частичным фрагментом (например, только секция `components:`). werf сформирует полный BOM-документ CycloneDX@1.6, объединив результаты сканирования с этим фрагментом. - -```yaml -project: werf-sbom-base-image-example -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: base-image -from: registry.werf.io/werf/scratch:latest -sbom: - fragment: | - components: - - type: library - name: openssl - version: "3.0.0" - purl: pkg:generic/openssl@3.0.0 - licenses: - - license: - id: Apache-2.0 - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 -``` - Результат сканирования будет сохранен как отдельный образ с постфиксом `-sbom` в локальном хранилище бекенда (например, Docker), а также отправлен в container registry, если указан флаг `--repo`. ### Свойства безопасности ГОСТ (`sbom.gost`) diff --git a/pkg/build/build_phase.go b/pkg/build/build_phase.go index 7bcc3c69c0..eb53362561 100644 --- a/pkg/build/build_phase.go +++ b/pkg/build/build_phase.go @@ -303,18 +303,15 @@ func (phase *BuildPhase) convergeImageSbom(ctx context.Context, name string, ima return err } - var fragmentBOM *cdx.BOM var gostConfig gost.Config if imgSbom := primaryImg.Sbom(); imgSbom != nil { - fragmentBOM = imgSbom.Document gostConfig = imgSbom.Gost } mergeOpts := cyclonedxutil.MergeOpts{ - BaseBOM: baseImageSbom, - ImportBOMs: importImageSboms, - FragmentBOM: fragmentBOM, - Gost: gostConfig, + BaseBOM: baseImageSbom, + ImportBOMs: importImageSboms, + Gost: gostConfig, } gitRepo := phase.Conveyor.GiterminismManager().LocalGitRepo() diff --git a/pkg/build/sbom_step.go b/pkg/build/sbom_step.go index c2cf4d358a..c3cd74db00 100644 --- a/pkg/build/sbom_step.go +++ b/pkg/build/sbom_step.go @@ -195,11 +195,5 @@ func (step *sbomStep) prepareGostComponents(ctx context.Context, mergeOpts *cycl } } - if mergeOpts.FragmentBOM != nil { - if err := gost.Upsert(mergeOpts.FragmentBOM, mergeOpts.Gost); err != nil { - return fmt.Errorf("set GOST properties for fragment BOM: %w", err) - } - } - return nil } diff --git a/pkg/config/raw_sbom.go b/pkg/config/raw_sbom.go index de8058640a..355d7958ae 100644 --- a/pkg/config/raw_sbom.go +++ b/pkg/config/raw_sbom.go @@ -1,17 +1,9 @@ package config -import ( - "fmt" - "strings" - - yamlv3 "gopkg.in/yaml.v3" -) - type rawSbom struct { doc *doc `yaml:"-"` - Fragment *string `yaml:"fragment,omitempty"` - Gost *rawGost `yaml:"gost,omitempty"` + Gost *rawGost `yaml:"gost,omitempty"` UnsupportedAttributes map[string]interface{} `yaml:",inline"` } @@ -41,13 +33,6 @@ func (s *rawSbom) UnmarshalYAML(unmarshal func(interface{}) error) error { return err } - // Soft validation (YAML-level only): - // If `sbom:` section is present, fragment must be present and non-empty, - // and must be valid YAML. - if err := s.validateFragmentYAML(); err != nil { - return err - } - return nil } @@ -58,23 +43,3 @@ func (s *rawSbom) docForErrors() *doc { // Fallback: avoid panics in error formatting in unexpected edge cases. return &doc{Content: []byte{}} } - -func (s *rawSbom) validateFragmentYAML() error { - d := s.docForErrors() - - if s.Fragment == nil || strings.TrimSpace(*s.Fragment) == "" { - return newDetailedConfigError("`sbom.fragment` is required when `sbom:` section is specified and must not be empty!", nil, d) - } - - // Validate fragment YAML by parsing with yaml.v3. - // We expect a YAML mapping at the root (e.g. `components: ...` or a full BOM document). - var fragment map[string]any - if err := yamlv3.Unmarshal([]byte(*s.Fragment), &fragment); err != nil { - return newDetailedConfigError(fmt.Sprintf("`sbom.fragment` must be valid YAML: %s", err), nil, d) - } - if fragment == nil { - return newDetailedConfigError("`sbom.fragment` must be a YAML mapping (e.g. `components: ...`)", nil, d) - } - - return nil -} diff --git a/pkg/config/raw_sbom_test.go b/pkg/config/raw_sbom_test.go index 907e4fc5f2..9e7c87fe6d 100644 --- a/pkg/config/raw_sbom_test.go +++ b/pkg/config/raw_sbom_test.go @@ -12,7 +12,7 @@ import ( var _ = Describe("rawSbom (YAML-level validation)", func() { DescribeTable( - "fragment and gost validation when sbom section is present", + "gost validation and unknown field handling when sbom section is present", func(yamlMap map[string]interface{}, expectedSbomPresent bool, unmarshalMatcher, configErrMatcher OmegaMatcher) { // NOTE: global var used by UnmarshalYAML parent tracking across many config raw structs. parentStack = util.NewStack() @@ -53,88 +53,29 @@ var _ = Describe("rawSbom (YAML-level validation)", func() { ), Entry( - "should fail when sbom section exists but fragment is missing", + "should succeed when sbom section is empty", map[string]interface{}{ "image": "image1", "from": "alpine:3.20", "sbom": map[string]interface{}{}, }, - false, - HaveOccurred(), - BeTrue(), - ), - - Entry( - "should fail when sbom.fragment is empty", - map[string]interface{}{ - "image": "image1", - "from": "alpine:3.20", - "sbom": map[string]interface{}{ - "fragment": " ", - }, - }, - false, - HaveOccurred(), - BeTrue(), - ), - - Entry( - "should fail when sbom.fragment is not valid YAML", - map[string]interface{}{ - "image": "image1", - "from": "alpine:3.20", - "sbom": map[string]interface{}{ - "fragment": "components: [", - }, - }, - false, - HaveOccurred(), - BeTrue(), - ), - - Entry( - "should fail when sbom.fragment YAML root is not a mapping", - map[string]interface{}{ - "image": "image1", - "from": "alpine:3.20", - "sbom": map[string]interface{}{ - "fragment": "- a\n- b\n", - }, - }, - false, - HaveOccurred(), - BeTrue(), - ), - - Entry( - "should succeed when sbom.fragment contains valid YAML mapping", - map[string]interface{}{ - "image": "image1", - "from": "alpine:3.20", - "sbom": map[string]interface{}{ - "fragment": "components: []\n", - }, - }, true, Succeed(), BeFalse(), ), Entry( - "should succeed when sbom.fragment and gost are valid", + "should fail when sbom.fragment is specified (removed feature)", map[string]interface{}{ "image": "image1", "from": "alpine:3.20", "sbom": map[string]interface{}{ - "fragment": "components: []\n", - "gost": map[string]interface{}{ - "attackSurface": "yes", - }, + "fragment": "components: []", }, }, - true, - Succeed(), - BeFalse(), + false, + HaveOccurred(), + BeTrue(), ), Entry( @@ -143,7 +84,6 @@ var _ = Describe("rawSbom (YAML-level validation)", func() { "image": "image1", "from": "alpine:3.20", "sbom": map[string]interface{}{ - "fragment": "components: []\n", "gost": map[string]interface{}{ "attackSurface": "indirect", }, @@ -160,7 +100,6 @@ var _ = Describe("rawSbom (YAML-level validation)", func() { "image": "image1", "from": "alpine:3.20", "sbom": map[string]interface{}{ - "fragment": "components: []\n", "gost": map[string]interface{}{ "attackSurface": "invalid", }, diff --git a/pkg/config/sbom_image.go b/pkg/config/sbom_image.go index 26b9ac3bcf..d595e7d7c4 100644 --- a/pkg/config/sbom_image.go +++ b/pkg/config/sbom_image.go @@ -1,11 +1,7 @@ package config import ( - "fmt" - "strings" - sbomPkg "github.com/werf/werf/v2/pkg/sbom" - "github.com/werf/werf/v2/pkg/sbom/cyclonedxutil" ) // buildImageSbom builds image-level SBOM configuration based on meta build settings. @@ -35,48 +31,8 @@ func buildImageSbom(meta *Meta, raw *rawSbom, d *doc) (*Sbom, error) { gostConfig = gostConfig.Merge(raw.Gost.toConfig()) } - // If no image-level configs are provided, we return early with the inherited GOST configuration. - if raw == nil { - return &Sbom{ - Standard: sbomPkg.StandardTypeCycloneDX16, - Gost: gostConfig, - }, nil - } - - // Defensive check: meta-level validation currently allows only CycloneDX@1.6. - if metaSbom.Standard != sbomPkg.StandardTypeCycloneDX16 { - return nil, newDetailedConfigError( - fmt.Sprintf( - "unsupported sbom standard %q for image sbom (only %q is supported)", - metaSbom.Standard.String(), - sbomPkg.StandardTypeCycloneDX16.String(), - ), - nil, - d, - ) - } - - // If fragment is not specified, we return the configuration with GOST only. - if raw.Fragment == nil { - return &Sbom{ - Standard: sbomPkg.StandardTypeCycloneDX16, - Gost: gostConfig, - }, nil - } - - fragment := strings.TrimSpace(*raw.Fragment) - if fragment == "" { - return nil, newDetailedConfigError("`sbom.fragment` must not be empty if specified", nil, d) - } - - bom, err := cyclonedxutil.BuildCycloneDX16BOMFromYAMLFragment([]byte(fragment)) - if err != nil { - return nil, newDetailedConfigError(fmt.Sprintf("invalid `sbom.fragment`: %v", err), nil, d) - } - return &Sbom{ Standard: sbomPkg.StandardTypeCycloneDX16, - Document: bom, Gost: gostConfig, }, nil } diff --git a/pkg/config/sbom_image_test.go b/pkg/config/sbom_image_test.go index bb21b9111f..cb60d3a893 100644 --- a/pkg/config/sbom_image_test.go +++ b/pkg/config/sbom_image_test.go @@ -3,7 +3,6 @@ package config import ( "errors" - cdx "github.com/CycloneDX/cyclonedx-go" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" "github.com/samber/lo" @@ -12,8 +11,6 @@ import ( "github.com/werf/werf/v2/pkg/sbom/cyclonedxutil/gost" ) -func strPtr(s string) *string { return &s } - var _ = Describe("buildImageSbom", func() { DescribeTable("validate and build image sbom", func(meta *Meta, raw *rawSbom, d *doc, errMatcher OmegaMatcher, expectConfigErr bool, validate func(*Sbom)) { @@ -42,9 +39,7 @@ var _ = Describe("buildImageSbom", func() { }, }, }, - &rawSbom{ - Fragment: strPtr("components: []"), - }, + &rawSbom{}, &doc{RenderFilePath: "werf.yaml", Content: []byte("image: test")}, HaveOccurred(), true, @@ -148,60 +143,5 @@ var _ = Describe("buildImageSbom", func() { Expect(sbomDirective.Gost.SecurityFunction).To(Equal(gost.GostValueNo)) }, ), - Entry( - "should fail when build.sbom.enable=true and sbom.fragment is empty", - &Meta{ - Build: MetaBuild{ - Sbom: &MetaBuildSbom{ - Enable: true, - Standard: pkgsbom.StandardTypeCycloneDX16, - Gost: gost.DefaultConfig(), - }, - }, - }, - &rawSbom{ - Fragment: strPtr(" "), - }, - &doc{RenderFilePath: "werf.yaml", Content: []byte("image: test")}, - HaveOccurred(), - true, - nil, - ), - Entry( - "should succeed when build.sbom.enable=true and sbom.fragment contains valid YAML", - &Meta{ - Build: MetaBuild{ - Sbom: &MetaBuildSbom{ - Enable: true, - Standard: pkgsbom.StandardTypeCycloneDX16, - Gost: gost.DefaultConfig(), - }, - }, - }, - &rawSbom{ - Fragment: strPtr(` -components: - - type: library - name: openssl - version: "3.0.0" -`), - }, - &doc{RenderFilePath: "werf.yaml", Content: []byte("image: test")}, - Succeed(), - false, - func(sbomDirective *Sbom) { - Expect(sbomDirective).ToNot(BeNil()) - Expect(sbomDirective.Document).ToNot(BeNil()) - - Expect(sbomDirective.Document.BOMFormat).To(Equal(cdx.BOMFormat)) - Expect(sbomDirective.Document.SpecVersion).To(Equal(cdx.SpecVersion1_6)) - Expect(sbomDirective.Document.Version).To(BeNumerically(">=", 1)) - - Expect(sbomDirective.Document.Components).ToNot(BeNil()) - Expect(*sbomDirective.Document.Components).To(HaveLen(1)) - Expect((*sbomDirective.Document.Components)[0].Name).To(Equal("openssl")) - Expect(sbomDirective.Gost.AttackSurface).To(Equal(gost.GostValueYes)) - }, - ), ) }) diff --git a/pkg/sbom/cyclonedxutil/fragment.go b/pkg/sbom/cyclonedxutil/fragment.go deleted file mode 100644 index 34c76a585b..0000000000 --- a/pkg/sbom/cyclonedxutil/fragment.go +++ /dev/null @@ -1,81 +0,0 @@ -package cyclonedxutil - -import ( - "encoding/json" - "fmt" - - cdx "github.com/CycloneDX/cyclonedx-go" - "github.com/google/uuid" - jsonpatch "gopkg.in/evanphx/json-patch.v5" - syaml "sigs.k8s.io/yaml" -) - -// BuildCycloneDX16BOMFromYAMLFragment builds a CycloneDX BOM from a YAML document that can be: -// - a full CycloneDX BOM document, or -// - a partial "fragment" (e.g. only `components:`, `metadata:`, etc). -// -// Process: -// - convert YAML -> JSON (strict) as-is -// - create an empty BOM document based on the requested standard and encode it as JSON -// - merge fragment JSON into the base JSON using JSON Merge Patch (RFC 7396) -// - decode merged JSON into the resulting BOM document -func BuildCycloneDX16BOMFromYAMLFragment(fragmentYAML []byte) (*cdx.BOM, error) { - jsonFromYAML, err := syaml.YAMLToJSONStrict(fragmentYAML) - if err != nil { - return nil, fmt.Errorf("sbom: invalid YAML fragment: %w", err) - } - - // Create a base (empty) BOM for the requested standard. - baseBOM := NewBOM() - - baseJSON, err := json.Marshal(baseBOM) - if err != nil { - return nil, fmt.Errorf("sbom: failed to build base CycloneDX document: %w", err) - } - - // Merge fragment into base using JSON Merge Patch. - mergedJSON, err := jsonpatch.MergePatch(baseJSON, jsonFromYAML) - if err != nil { - return nil, fmt.Errorf("sbom: failed to merge YAML fragment into base document: %w", err) - } - - return BuildCycloneDX16BOMFromJSON(mergedJSON) -} - -// BuildCycloneDX16BOMFromJSON builds a CycloneDX BOM document from JSON bytes and validates it. -// Currently, only CycloneDX@1.6 is supported. -// -// External SBOMs may contain duplicate entries in arrays with "uniqueItems: true" -// (e.g. components). To handle this gracefully, the function first deserializes -// and deduplicates the BOM, then validates the cleaned result against the schema. -func BuildCycloneDX16BOMFromJSON(bomJSON []byte) (*cdx.BOM, error) { - var bom cdx.BOM - if err := json.Unmarshal(bomJSON, &bom); err != nil { - return nil, fmt.Errorf("cyclonedxutil: failed to decode CycloneDX document: %w", err) - } - - DedupBOM(&bom) - - cleanJSON, err := json.Marshal(bom) - if err != nil { - return nil, fmt.Errorf("cyclonedxutil: failed to re-encode BOM after dedup: %w", err) - } - - if err := ValidateCycloneDX16Schema(cleanJSON); err != nil { - return nil, fmt.Errorf("cyclonedxutil: validation failed: %w", err) - } - - // As a "hard-ish" check, ensure cyclonedx-go can encode the resulting BOM as JSON 1.6. - if _, err := ToJSON(&bom); err != nil { - return nil, fmt.Errorf("cyclonedxutil: failed to encode BOM for specVersion 1.6: %w", err) - } - - return &bom, nil -} - -// NewBOM creates a new CycloneDX 1.6 BOM with a unique serial number. -func NewBOM() *cdx.BOM { - bom := cdx.NewBOM() - bom.SerialNumber = "urn:uuid:" + uuid.New().String() - return bom -} diff --git a/pkg/sbom/cyclonedxutil/fragment_test.go b/pkg/sbom/cyclonedxutil/fragment_test.go deleted file mode 100644 index 676bf0b834..0000000000 --- a/pkg/sbom/cyclonedxutil/fragment_test.go +++ /dev/null @@ -1,120 +0,0 @@ -package cyclonedxutil - -import ( - "strings" - - cdx "github.com/CycloneDX/cyclonedx-go" - "github.com/google/uuid" - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/types" -) - -type bomAssert func(*cdx.BOM) - -var _ = Describe("SBOM CycloneDX builders", func() { - Describe("BuildCycloneDX16BOMFromYAMLFragment", func() { - DescribeTable( - "builds/validates BOM from YAML fragment", - func(fragmentYAML string, expectedErrMatcher types.GomegaMatcher, expectedBom bomAssert) { - bom, err := BuildCycloneDX16BOMFromYAMLFragment([]byte(fragmentYAML)) - - Expect(err).To(expectedErrMatcher) - if err != nil { - return - } - - Expect(bom).ToNot(BeNil()) - Expect(bom.BOMFormat).To(Equal(cdx.BOMFormat)) - Expect(bom.SpecVersion).To(Equal(cdx.SpecVersion1_6)) - Expect(bom.Version).To(BeNumerically(">=", 1)) - - if expectedBom != nil { - expectedBom(bom) - } - }, - - Entry( - "invalid YAML", - "a: [", - MatchError(ContainSubstring("invalid YAML fragment")), - nil, - ), - - Entry( - "succeeds for a valid fragment (components)", - ` -components: - - type: library - name: openssl - version: "3.0.0" -`, - Succeed(), - bomAssert(func(b *cdx.BOM) { - Expect(b.SerialNumber).To(HavePrefix("urn:uuid:")) - _, parseErr := uuid.Parse(strings.TrimPrefix(b.SerialNumber, "urn:uuid:")) - Expect(parseErr).ToNot(HaveOccurred()) - - Expect(b.Components).ToNot(BeNil()) - Expect(*b.Components).To(HaveLen(1)) - Expect((*b.Components)[0].Name).To(Equal("openssl")) - Expect((*b.Components)[0].Type).To(Equal(cdx.ComponentTypeLibrary)) - Expect((*b.Components)[0].Version).To(Equal("3.0.0")) - }), - ), - Entry( - "allows overriding base fields via full document YAML (version and serialNumber)", - ` -bomFormat: CycloneDX -specVersion: "1.6" -version: 7 -serialNumber: "urn:uuid:00000000-0000-0000-0000-000000000000" -components: - - type: library - name: zlib -`, - Succeed(), - bomAssert(func(b *cdx.BOM) { - Expect(b.Version).To(Equal(7)) - Expect(b.SerialNumber).To(Equal("urn:uuid:00000000-0000-0000-0000-000000000000")) - }), - ), - ) - }) - - Describe("BuildCycloneDX16BOMFromJSON", func() { - It("deduplicates components in external SBOM before validation", func() { - inputJSON := `{ - "bomFormat": "CycloneDX", - "specVersion": "1.6", - "version": 1, - "serialNumber": "urn:uuid:00000000-0000-0000-0000-000000000000", - "components": [ - {"type": "library", "name": "openssl", "version": "3.0.0"}, - {"type": "library", "name": "openssl", "version": "3.0.0"}, - {"type": "library", "name": "zlib", "version": "1.2.13"} - ] - }` - - bom, err := BuildCycloneDX16BOMFromJSON([]byte(inputJSON)) - Expect(err).ToNot(HaveOccurred()) - Expect(bom).ToNot(BeNil()) - Expect(bom.Components).ToNot(BeNil()) - Expect(*bom.Components).To(HaveLen(2)) - Expect((*bom.Components)[0].Name).To(Equal("openssl")) - Expect((*bom.Components)[1].Name).To(Equal("zlib")) - }) - - It("rejects invalid BOM even after dedup", func() { - inputJSON := `{ - "bomFormat": "INVALID", - "specVersion": "1.6", - "version": 1 - }` - - _, err := BuildCycloneDX16BOMFromJSON([]byte(inputJSON)) - Expect(err).To(HaveOccurred()) - Expect(err.Error()).To(ContainSubstring("validation failed")) - }) - }) -}) diff --git a/pkg/sbom/cyclonedxutil/json.go b/pkg/sbom/cyclonedxutil/json.go index 2b42b2c3d5..8addb21e9b 100644 --- a/pkg/sbom/cyclonedxutil/json.go +++ b/pkg/sbom/cyclonedxutil/json.go @@ -2,6 +2,7 @@ package cyclonedxutil import ( "bytes" + "fmt" cdx "github.com/CycloneDX/cyclonedx-go" ) @@ -15,3 +16,17 @@ func ToJSON(bom *cdx.BOM) ([]byte, error) { return buf.Bytes(), nil } + +// BuildCycloneDX16BOMFromJSON builds a CycloneDX 1.6 BOM from JSON bytes. +func BuildCycloneDX16BOMFromJSON(data []byte) (*cdx.BOM, error) { + bom := &cdx.BOM{} + if err := cdx.NewBOMDecoder(bytes.NewReader(data), cdx.BOMFileFormatJSON).Decode(bom); err != nil { + return nil, fmt.Errorf("sbom: invalid CycloneDX JSON: %w", err) + } + + if bom.SpecVersion != cdx.SpecVersion1_6 { + return nil, fmt.Errorf("sbom: unsupported CycloneDX spec version %q (expected %q)", bom.SpecVersion, cdx.SpecVersion1_6) + } + + return bom, nil +} diff --git a/pkg/sbom/cyclonedxutil/merge.go b/pkg/sbom/cyclonedxutil/merge.go index 997d23d44b..89144098b8 100644 --- a/pkg/sbom/cyclonedxutil/merge.go +++ b/pkg/sbom/cyclonedxutil/merge.go @@ -6,25 +6,25 @@ import ( "strings" cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/google/uuid" "github.com/werf/common-go/pkg/util" "github.com/werf/werf/v2/pkg/sbom/cyclonedxutil/gost" ) type MergeOpts struct { - BaseBOM *cdx.BOM - ImportBOMs []*cdx.BOM - FragmentBOM *cdx.BOM - Gost gost.Config + BaseBOM *cdx.BOM + ImportBOMs []*cdx.BOM + Gost gost.Config } func (o MergeOpts) IsEmpty() bool { - return o.BaseBOM == nil && len(o.ImportBOMs) == 0 && o.FragmentBOM == nil + return o.BaseBOM == nil && len(o.ImportBOMs) == 0 } func (o MergeOpts) Checksum() string { var parts []string - for _, bom := range append([]*cdx.BOM{o.BaseBOM, o.FragmentBOM}, o.ImportBOMs...) { + for _, bom := range append([]*cdx.BOM{o.BaseBOM}, o.ImportBOMs...) { if cs := StableBOMChecksum(bom); cs != "" { parts = append(parts, cs) } @@ -34,10 +34,10 @@ func (o MergeOpts) Checksum() string { } func (o MergeOpts) mergeOrder(target *cdx.BOM) []*cdx.BOM { - boms := make([]*cdx.BOM, 0, len(o.ImportBOMs)+3) + boms := make([]*cdx.BOM, 0, len(o.ImportBOMs)+2) boms = append(boms, o.BaseBOM) boms = append(boms, o.ImportBOMs...) - boms = append(boms, o.FragmentBOM, target) + boms = append(boms, target) return boms } @@ -200,6 +200,20 @@ func mergeDeclarations(boms []*cdx.BOM) *cdx.Declarations { return &result } +// NewBOM creates a new empty CycloneDX 1.6 BOM with standard fields initialized. +func NewBOM() *cdx.BOM { + return &cdx.BOM{ + BOMFormat: cdx.BOMFormat, + SpecVersion: cdx.SpecVersion1_6, + Version: 1, + SerialNumber: newSerialNumber(), + } +} + +func newSerialNumber() string { + return "urn:uuid:" + uuid.New().String() +} + func appendBOMComponents(dest []cdx.Component, bom *cdx.BOM) []cdx.Component { if bom != nil && bom.Components != nil { return append(dest, *bom.Components...) diff --git a/pkg/sbom/cyclonedxutil/merge_test.go b/pkg/sbom/cyclonedxutil/merge_test.go index 1ea91fcf3e..4e3c966b3f 100644 --- a/pkg/sbom/cyclonedxutil/merge_test.go +++ b/pkg/sbom/cyclonedxutil/merge_test.go @@ -33,7 +33,7 @@ func dependencyRefs(bom *cdx.BOM) []string { } var _ = Describe("MergeBOMs", func() { - It("concatenates components in order", func() { + It("concatenates components in merge order (base → imports → target)", func() { baseBOM := &cdx.BOM{ SpecVersion: cdx.SpecVersion1_6, Components: &[]cdx.Component{ @@ -53,12 +53,6 @@ var _ = Describe("MergeBOMs", func() { {Name: "import2-comp", Version: "1.0.0"}, }, } - fragmentBOM := &cdx.BOM{ - SpecVersion: cdx.SpecVersion1_6, - Components: &[]cdx.Component{ - {Name: "fragment-comp", Version: "1.0.0"}, - }, - } targetBOM := &cdx.BOM{ SpecVersion: cdx.SpecVersion1_6, Components: &[]cdx.Component{ @@ -67,9 +61,8 @@ var _ = Describe("MergeBOMs", func() { } result, err := MergeBOMs(targetBOM, MergeOpts{ - BaseBOM: baseBOM, - ImportBOMs: []*cdx.BOM{importBOM1, importBOM2}, - FragmentBOM: fragmentBOM, + BaseBOM: baseBOM, + ImportBOMs: []*cdx.BOM{importBOM1, importBOM2}, }) Expect(err).ToNot(HaveOccurred()) @@ -79,7 +72,6 @@ var _ = Describe("MergeBOMs", func() { "base-comp-2", "import1-comp", "import2-comp", - "fragment-comp", "target-comp", })) }) @@ -233,7 +225,7 @@ var _ = Describe("MergeBOMs", func() { assert(result) }, - Entry("concatenates in merge order (base → imports → fragment → target)", + Entry("concatenates in merge order (base → imports → target)", &cdx.BOM{ SpecVersion: cdx.SpecVersion1_6, Dependencies: &[]cdx.Dependency{ @@ -252,16 +244,11 @@ var _ = Describe("MergeBOMs", func() { {SpecVersion: cdx.SpecVersion1_6, Dependencies: &[]cdx.Dependency{{Ref: "import1-ref"}}}, {SpecVersion: cdx.SpecVersion1_6, Dependencies: &[]cdx.Dependency{{Ref: "import2-ref"}}}, }, - FragmentBOM: &cdx.BOM{ - SpecVersion: cdx.SpecVersion1_6, - Dependencies: &[]cdx.Dependency{{Ref: "fragment-ref"}}, - }, }, func(result *cdx.BOM) { Expect(dependencyRefs(result)).To(Equal([]string{ "base-ref-1", "base-ref-2", "import1-ref", "import2-ref", - "fragment-ref", "target-ref", })) }, @@ -533,7 +520,6 @@ var _ = Describe("MergeOpts", func() { Entry("empty opts", MergeOpts{}, true), Entry("with base BOM", MergeOpts{BaseBOM: &cdx.BOM{}}, false), Entry("with import BOMs", MergeOpts{ImportBOMs: []*cdx.BOM{{}}}, false), - Entry("with fragment BOM", MergeOpts{FragmentBOM: &cdx.BOM{}}, false), Entry("with empty import slice", MergeOpts{ImportBOMs: []*cdx.BOM{}}, true), ) diff --git a/test/e2e/build/_fixtures/sbom/cross_project/base/werf.yaml b/test/e2e/build/_fixtures/sbom/cross_project/base/werf.yaml deleted file mode 100644 index 38532c8717..0000000000 --- a/test/e2e/build/_fixtures/sbom/cross_project/base/werf.yaml +++ /dev/null @@ -1,34 +0,0 @@ -project: werf-sbom-cross-project-base -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: base-level-0 -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: curl - bom-ref: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: Apache-2.0 - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 - - name: libcurl - bom-ref: libcurl - type: library - version: 8.12.1 - purl: pkg:generic/libcurl@8.12.1 - - dependencies: - - ref: curl - dependsOn: - - libcurl - diff --git a/test/e2e/build/_fixtures/sbom/cross_project/derived/file b/test/e2e/build/_fixtures/sbom/cross_project/derived/file deleted file mode 100644 index deb02c1a0d..0000000000 --- a/test/e2e/build/_fixtures/sbom/cross_project/derived/file +++ /dev/null @@ -1 +0,0 @@ -file-content diff --git a/test/e2e/build/_fixtures/sbom/cross_project/derived/werf-giterminism.yaml b/test/e2e/build/_fixtures/sbom/cross_project/derived/werf-giterminism.yaml deleted file mode 100644 index c4ee0a2014..0000000000 --- a/test/e2e/build/_fixtures/sbom/cross_project/derived/werf-giterminism.yaml +++ /dev/null @@ -1,5 +0,0 @@ -giterminismConfigVersion: 1 -config: - goTemplateRendering: - allowEnvVariables: - - BASE_IMAGE_REF diff --git a/test/e2e/build/_fixtures/sbom/cross_project/derived/werf.yaml b/test/e2e/build/_fixtures/sbom/cross_project/derived/werf.yaml deleted file mode 100644 index 6548242375..0000000000 --- a/test/e2e/build/_fixtures/sbom/cross_project/derived/werf.yaml +++ /dev/null @@ -1,31 +0,0 @@ -project: werf-sbom-cross-project-derived -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: derived-level-1 -from: {{ env "BASE_IMAGE_REF" }} -sbom: - fragment: | - components: - - name: derived-component - bom-ref: derived-component - type: application - version: 1.0.0 - purl: pkg:generic/derived-component@1.0.0 - - name: derived-dep - bom-ref: derived-dep - type: library - version: 1.0.0 - purl: pkg:generic/derived-dep@1.0.0 - - dependencies: - - ref: derived-component - dependsOn: - - derived-dep -git: - - add: /file - to: /state0/file - diff --git a/test/e2e/build/_fixtures/sbom/gost_meta_image/werf.yaml b/test/e2e/build/_fixtures/sbom/gost_meta_image/werf.yaml index 6d43402033..b3c006e2cc 100644 --- a/test/e2e/build/_fixtures/sbom/gost_meta_image/werf.yaml +++ b/test/e2e/build/_fixtures/sbom/gost_meta_image/werf.yaml @@ -11,8 +11,6 @@ build: image: app from: registry.werf.io/werf/scratch sbom: - fragment: | - components: [] gost: attackSurface: no securityFunction: yes diff --git a/test/e2e/build/_fixtures/sbom/import_stapel/state0/file b/test/e2e/build/_fixtures/sbom/import_stapel/state0/file deleted file mode 100644 index deb02c1a0d..0000000000 --- a/test/e2e/build/_fixtures/sbom/import_stapel/state0/file +++ /dev/null @@ -1 +0,0 @@ -file-content diff --git a/test/e2e/build/_fixtures/sbom/import_stapel/state0/werf.yaml b/test/e2e/build/_fixtures/sbom/import_stapel/state0/werf.yaml deleted file mode 100644 index 533acad7ad..0000000000 --- a/test/e2e/build/_fixtures/sbom/import_stapel/state0/werf.yaml +++ /dev/null @@ -1,46 +0,0 @@ -project: werf-test-e2e-build-sbom-import-stapel -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: stapel-scratch-based -from: registry.werf.io/werf/scratch -git: - - add: /file - to: /state0/file -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 ---- -image: stapel -from: registry.werf.io/werf/scratch -import: - - image: stapel-scratch-based - add: /state0/file - to: /state0/file - after: install -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 diff --git a/test/e2e/build/_fixtures/sbom/import_stapel/state1/file b/test/e2e/build/_fixtures/sbom/import_stapel/state1/file deleted file mode 100644 index deb02c1a0d..0000000000 --- a/test/e2e/build/_fixtures/sbom/import_stapel/state1/file +++ /dev/null @@ -1 +0,0 @@ -file-content diff --git a/test/e2e/build/_fixtures/sbom/import_stapel/state1/werf.yaml b/test/e2e/build/_fixtures/sbom/import_stapel/state1/werf.yaml deleted file mode 100644 index 8c943678a1..0000000000 --- a/test/e2e/build/_fixtures/sbom/import_stapel/state1/werf.yaml +++ /dev/null @@ -1,46 +0,0 @@ -project: werf-test-e2e-build-sbom-import-stapel -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: stapel-scratch-based -from: registry.werf.io/werf/scratch -git: - - add: /file - to: /state0/file -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 ---- -image: stapel -from: registry.werf.io/werf/scratch -import: - - image: registry.werf.io/base/ubuntu:22.04 - add: /etc/os-release - to: /state1/os-release - after: install -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 diff --git a/test/e2e/build/_fixtures/sbom/merge_base_fragment/werf.yaml b/test/e2e/build/_fixtures/sbom/merge_base_fragment/werf.yaml deleted file mode 100644 index a3a4f1943f..0000000000 --- a/test/e2e/build/_fixtures/sbom/merge_base_fragment/werf.yaml +++ /dev/null @@ -1,72 +0,0 @@ -project: werf-test-e2e-build-sbom-merge-base-fragment -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: app -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: custom-component - bom-ref: custom-component - type: application - version: 1.0.0 - purl: pkg:generic/custom-component@1.0.0 - - name: dep-a - bom-ref: dep-a - type: library - version: 1.0.0 - purl: pkg:generic/dep-a@1.0.0 - - name: dep-b - bom-ref: dep-b - type: library - version: 2.0.0 - purl: pkg:generic/dep-b@2.0.0 - - services: - - name: custom-api-service - endpoints: - - https://api.example.com/v1 - authenticated: true - x-trust-boundary: false - - externalReferences: - - type: website - url: https://example.com - - type: documentation - url: https://docs.example.com - - properties: - - name: build-environment - value: production - - name: custom-property - value: custom-value - - dependencies: - - ref: custom-component - dependsOn: - - dep-a - - dep-b - - declarations: - assessors: - - bom-ref: fragment-assessor - thirdParty: true - organization: - name: "Fragment Assessor Org" - claims: - - bom-ref: fragment-claim - target: custom-component - predicate: "Component is verified" - - annotations: - - text: "This is a test annotation for merge verification" - annotator: - organization: - name: "werf" - timestamp: "2024-05-24T12:00:00Z" - subjects: - - "urn:cdx:custom-component/1.0.0" diff --git a/test/e2e/build/_fixtures/sbom/merge_derived_with_base_fragment/file b/test/e2e/build/_fixtures/sbom/merge_derived_with_base_fragment/file deleted file mode 100644 index dd59d09863..0000000000 --- a/test/e2e/build/_fixtures/sbom/merge_derived_with_base_fragment/file +++ /dev/null @@ -1 +0,0 @@ -file content diff --git a/test/e2e/build/_fixtures/sbom/merge_derived_with_base_fragment/werf.yaml b/test/e2e/build/_fixtures/sbom/merge_derived_with_base_fragment/werf.yaml deleted file mode 100644 index 7dd0a373be..0000000000 --- a/test/e2e/build/_fixtures/sbom/merge_derived_with_base_fragment/werf.yaml +++ /dev/null @@ -1,42 +0,0 @@ -project: werf-sbom-test -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: base-level-0 -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: curl - bom-ref: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: Apache-2.0 - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 - - name: libcurl - bom-ref: libcurl - type: library - version: 8.12.1 - purl: pkg:generic/libcurl@8.12.1 - - dependencies: - - ref: curl - dependsOn: - - libcurl ---- -image: derived-level-1 -from: base-level-0 -sbom: - fragment: | - components: [] -git: - - add: /file - to: /state0/file diff --git a/test/e2e/build/_fixtures/sbom/merge_full/werf.yaml b/test/e2e/build/_fixtures/sbom/merge_full/werf.yaml deleted file mode 100644 index 067ccd8c02..0000000000 --- a/test/e2e/build/_fixtures/sbom/merge_full/werf.yaml +++ /dev/null @@ -1,122 +0,0 @@ -project: werf-test-e2e-build-sbom-merge-full -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: builder -from: registry.werf.io/werf/scratch -git: - - add: / - to: /src -sbom: - fragment: | - components: - - name: builder-custom - bom-ref: builder-custom - type: library - version: 1.0.0 - purl: pkg:generic/builder-custom@1.0.0 - - name: builder-dep - bom-ref: builder-dep - type: library - version: 1.0.0 - purl: pkg:generic/builder-dep@1.0.0 - - services: - - name: builder-service - endpoints: - - https://builder.example.com - - dependencies: - - ref: builder-custom - dependsOn: - - builder-dep - - declarations: - assessors: - - bom-ref: builder-assessor - thirdParty: false - organization: - name: "Builder Org" - claims: - - bom-ref: builder-claim - target: builder-custom - predicate: "Build dependency verified" - - properties: - - name: builder-property - value: builder-value - - name: builder-stage - value: build ---- -image: app -from: registry.werf.io/werf/scratch -import: - - image: builder - add: /src - to: /app - after: install -sbom: - fragment: | - components: - - name: app-custom - bom-ref: app-custom - type: application - version: 2.0.0 - purl: pkg:generic/app-custom@2.0.0 - - name: app-dep-a - bom-ref: app-dep-a - type: library - version: 1.0.0 - purl: pkg:generic/app-dep-a@1.0.0 - - name: app-dep-b - bom-ref: app-dep-b - type: library - version: 2.0.0 - purl: pkg:generic/app-dep-b@2.0.0 - - services: - - name: app-api-service - endpoints: - - https://app.example.com/api - authenticated: true - - externalReferences: - - type: vcs - url: https://github.com/example/app - - type: issue-tracker - url: https://github.com/example/app/issues - - properties: - - name: app-property - value: app-value - - name: environment - value: production - - dependencies: - - ref: app-custom - dependsOn: - - app-dep-a - - app-dep-b - - declarations: - assessors: - - bom-ref: app-assessor - thirdParty: true - organization: - name: "App Security Org" - claims: - - bom-ref: app-claim - target: app-custom - predicate: "Application is compliant" - - annotations: - - text: "Application level annotation" - annotator: - organization: - name: "werf" - timestamp: "2024-05-24T12:00:00Z" - subjects: - - "pkg:generic/app-custom@2.0.0" diff --git a/test/e2e/build/_fixtures/sbom/state0/werf.yaml b/test/e2e/build/_fixtures/sbom/state0/werf.yaml deleted file mode 100644 index 009053020c..0000000000 --- a/test/e2e/build/_fixtures/sbom/state0/werf.yaml +++ /dev/null @@ -1,22 +0,0 @@ -project: werf-test-e2e-build-sbom -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: stapel -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 diff --git a/test/e2e/build/_fixtures/sbom/state1/werf.yaml b/test/e2e/build/_fixtures/sbom/state1/werf.yaml deleted file mode 100644 index 3665e42de1..0000000000 --- a/test/e2e/build/_fixtures/sbom/state1/werf.yaml +++ /dev/null @@ -1,22 +0,0 @@ -project: werf-test-e2e-build-sbom-base-stapel -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: stapel-scratch-based -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 diff --git a/test/e2e/build/_fixtures/sbom/state2/werf.yaml b/test/e2e/build/_fixtures/sbom/state2/werf.yaml deleted file mode 100644 index 5323ad237a..0000000000 --- a/test/e2e/build/_fixtures/sbom/state2/werf.yaml +++ /dev/null @@ -1,22 +0,0 @@ -project: werf-test-e2e-build-sbom-base-stapel -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: stapel-ubuntu-based -from: registry.werf.io/base/ubuntu:22.04 -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 diff --git a/test/e2e/build/sbom_test.go b/test/e2e/build/sbom_test.go index e337c77833..7c08cd9025 100644 --- a/test/e2e/build/sbom_test.go +++ b/test/e2e/build/sbom_test.go @@ -14,374 +14,6 @@ import ( const sbomProcessingPrefix = "SBOM processing" -var _ = Describe("Simple build", Label("e2e", "build", "sbom", "simple"), func() { - DescribeTable("should succeed with registry-only SBOM", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("state0: preparing test repo") - repoDirname := "repo0" - fixtureRelPath := "sbom/state0" - buildReportName := "report0.json" - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("state0: building images") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath(buildReportName), nil) - Expect(buildOut).To(ContainSubstring("Building stage")) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}, FlakeAttempts(5)), - ) - - DescribeTable("should succeed with registry-only SBOM when base image SBOM is scratch", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirname := "repo_base_sbom" - fixtureRelPath := "sbom/state1" - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("building images") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_base_sbom.json"), nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - - DescribeTable("should fail when base image has no SBOM and is not a trusted builder image", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirname := "repo_base_sbom" - fixtureRelPath := "sbom/state2" - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("building images expecting failure") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - buildOut := werfProject.Build(ctx, &werf.BuildOptions{CommonOptions: werf.CommonOptions{ShouldFail: true}}) - Expect(buildOut).To(ContainSubstring("unable to get base image sbom")) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - - DescribeTable("should succeed with registry-only SBOM for import stapel", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirName := "repo_import_sbom" - fixtureRelPath := "sbom/import_stapel/state0" - SuiteData.InitTestRepo(ctx, repoDirName, fixtureRelPath) - - By("building images") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirName)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_import_sbom.json"), nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - - DescribeTable("should fail when external import image has no SBOM and is not a trusted builder image", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirName := "repo_import_sbom" - fixtureRelPath := "sbom/import_stapel/state1" - SuiteData.InitTestRepo(ctx, repoDirName, fixtureRelPath) - - By("building images expecting failure") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirName)) - buildOut := werfProject.Build(ctx, &werf.BuildOptions{CommonOptions: werf.CommonOptions{ShouldFail: true}}) - Expect(buildOut).To(ContainSubstring("unable to get import image sbom")) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) -}) - -var _ = Describe("SBOM merge", Label("e2e", "build", "sbom", "merge", "simple"), func() { - DescribeTable("should succeed with registry-only SBOM for base+fragment merge", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirname := "repo_merge_base_fragment" - fixtureRelPath := "sbom/merge_base_fragment" - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("building images") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_merge_base_fragment.json"), nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - - DescribeTable("should succeed with registry-only SBOM for derived+base merge", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirname := "repo_merge_derived_with_base" - fixtureRelPath := "sbom/merge_derived_with_base_fragment" - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("building images") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_merge_derived_with_base.json"), nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - - DescribeTable("should succeed with registry-only SBOM for full merge (base+import+fragment)", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("preparing test repo") - repoDirname := "repo_merge_full" - fixtureRelPath := "sbom/merge_full" - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("building images") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_merge_full.json"), nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) -}) - -var _ = Describe("SBOM cross-project merge", Label("e2e", "build", "sbom", "merge", "simple"), func() { - DescribeTable("should succeed with registry-only SBOM for cross-project merge", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - By("Step 1: building base project") - baseRepoDirname := "repo_cross_project_base" - baseFixtureRelPath := "sbom/cross_project/base" - SuiteData.InitTestRepo(ctx, baseRepoDirname, baseFixtureRelPath) - - baseWerfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(baseRepoDirname)) - baseReportProject := report.NewProjectWithReport(baseWerfProject) - baseBuildOut, baseBuildReport := baseReportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_cross_project_base.json"), nil) - Expect(baseBuildOut).To(ContainSubstring(sbomProcessingPrefix)) - - baseReportRecord, ok := baseBuildReport.Images["base-level-0"] - Expect(ok).To(BeTrue(), "base-level-0 should be in build report") - - By("Step 2: building derived project with BASE_IMAGE_REF env") - derivedRepoDirname := "repo_cross_project_derived" - derivedFixtureRelPath := "sbom/cross_project/derived" - SuiteData.InitTestRepo(ctx, derivedRepoDirname, derivedFixtureRelPath) - - derivedWerfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(derivedRepoDirname)) - derivedReportProject := report.NewProjectWithReport(derivedWerfProject) - - derivedBuildOpts := &werf.WithReportOptions{ - CommonOptions: werf.CommonOptions{ - Envs: []string{ - fmt.Sprintf("BASE_IMAGE_REF=%s", baseReportRecord.DockerImageName), - }, - }, - } - derivedBuildOut, _ := derivedReportProject.BuildWithReport(ctx, SuiteData.GetBuildReportPath("report_cross_project_derived.json"), derivedBuildOpts) - Expect(derivedBuildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) -}) - var _ = Describe("GOST SBOM fields", Label("e2e", "build", "sbom", "gost", "simple"), func() { DescribeTable("should succeed with registry-only SBOM for GOST fields", func(ctx SpecContext, testOpts simpleTestOptions, fixtureRelPath, repoDirname string) { diff --git a/test/e2e/sbom/_fixtures/state0/Dockerfile b/test/e2e/sbom/_fixtures/state0/Dockerfile deleted file mode 100644 index b097ae1aad..0000000000 --- a/test/e2e/sbom/_fixtures/state0/Dockerfile +++ /dev/null @@ -1,5 +0,0 @@ -FROM registry.werf.io/base/alpine - -COPY file /file - -RUN touch /created-by-run diff --git a/test/e2e/sbom/_fixtures/state0/werf.yaml b/test/e2e/sbom/_fixtures/state0/werf.yaml deleted file mode 100644 index c2ff09492a..0000000000 --- a/test/e2e/sbom/_fixtures/state0/werf.yaml +++ /dev/null @@ -1,22 +0,0 @@ -project: werf-test-e2e-sbom-get -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: stapel -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: curl - type: application - version: 8.12.1 - purl: pkg:generic/curl@8.12.1 - licenses: - - license: - id: MIT - hashes: - - alg: SHA-256 - content: 9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08 diff --git a/test/e2e/sbom/_fixtures/state1/werf.yaml b/test/e2e/sbom/_fixtures/state1/werf.yaml deleted file mode 100644 index 5e9972e646..0000000000 --- a/test/e2e/sbom/_fixtures/state1/werf.yaml +++ /dev/null @@ -1,62 +0,0 @@ -project: werf-test-e2e-sbom-merge -configVersion: 1 -build: - sbom: - enable: true - standard: cyclonedx@1.6 ---- -image: frontend -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: lodash - type: library - version: 4.17.21 - purl: pkg:npm/lodash@4.17.21 - properties: - - name: "GOST:attack_surface" - value: "yes" - - name: "GOST:security_function" - value: "no" - - name: axios - type: library - version: 1.6.0 - purl: pkg:npm/axios@1.6.0 - properties: - - name: "GOST:attack_surface" - value: "no" - - name: "GOST:security_function" - value: "no" - dependencies: - - ref: pkg:npm/lodash@4.17.21 - dependsOn: - - pkg:npm/axios@1.6.0 ---- -image: backend -from: registry.werf.io/werf/scratch -sbom: - fragment: | - components: - - name: lodash - type: library - version: 4.17.21 - purl: pkg:npm/lodash@4.17.21 - properties: - - name: "GOST:attack_surface" - value: "yes" - - name: "GOST:security_function" - value: "no" - - name: express - type: library - version: 4.18.2 - purl: pkg:npm/express@4.18.2 - properties: - - name: "GOST:attack_surface" - value: "no" - - name: "GOST:security_function" - value: "yes" - dependencies: - - ref: pkg:npm/express@4.18.2 - dependsOn: - - pkg:npm/lodash@4.17.21 diff --git a/test/e2e/sbom/common_test.go b/test/e2e/sbom/common_test.go deleted file mode 100644 index 9e51cc0602..0000000000 --- a/test/e2e/sbom/common_test.go +++ /dev/null @@ -1,52 +0,0 @@ -package e2e_build_test - -import ( - "strings" - - "github.com/werf/werf/v2/test/pkg/externalrefmock" - "github.com/werf/werf/v2/test/pkg/suite_init" -) - -type setupEnvOptions struct { - ContainerBackendMode string - WithLocalRepo bool - WithStagedDockerfileBuilder bool - State string -} - -type simpleTestOptions struct { - setupEnvOptions -} - -func setupEnv(opts setupEnvOptions) { - if opts.ContainerBackendMode == "docker" || strings.HasSuffix(opts.ContainerBackendMode, "-docker") { - SuiteData.Stubs.SetEnv("WERF_BUILDAH_MODE", "docker") - } else { - SuiteData.Stubs.SetEnv("WERF_BUILDAH_MODE", opts.ContainerBackendMode) - } - - SuiteData.Stubs.SetEnv("WERF_REPO", suite_init.TestRepo(SuiteData.ProjectName)) - SuiteData.Stubs.SetEnv("WERF_EXTERNAL_REFS_SERVER_URL", externalrefmock.Start().URL) - - if opts.ContainerBackendMode == "buildkit-docker" { - SuiteData.Stubs.SetEnv("DOCKER_BUILDKIT", "1") - } else { - SuiteData.Stubs.UnsetEnv("DOCKER_BUILDKIT") - } - - if opts.WithLocalRepo { - SuiteData.Stubs.SetEnv("WERF_INSECURE_REGISTRY", "1") - SuiteData.Stubs.SetEnv("WERF_SKIP_TLS_VERIFY_REGISTRY", "1") - } else { - SuiteData.Stubs.UnsetEnv("WERF_INSECURE_REGISTRY") - SuiteData.Stubs.UnsetEnv("WERF_SKIP_TLS_VERIFY_REGISTRY") - } - - if opts.WithStagedDockerfileBuilder { - SuiteData.Stubs.SetEnv("WERF_FORCE_STAGED_DOCKERFILE", "1") - } else { - SuiteData.Stubs.UnsetEnv("WERF_FORCE_STAGED_DOCKERFILE") - } - - SuiteData.Stubs.SetEnv("ENV_SECRET", "WERF_BUILD_SECRET") -} diff --git a/test/e2e/sbom/get_test.go b/test/e2e/sbom/get_test.go deleted file mode 100644 index 6fd8dd715f..0000000000 --- a/test/e2e/sbom/get_test.go +++ /dev/null @@ -1,159 +0,0 @@ -package e2e_build_test - -import ( - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/werf/werf/v2/test/pkg/werf" -) - -const sbomProcessingPrefix = "SBOM processing" - -var _ = Describe("Sbom get", Label("e2e", "sbom", "get", "simple"), func() { - Describe("default", func() { - DescribeTable("should succeed with registry-only SBOM generation", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - repoDirname := "repo0" - fixtureRelPath := "state0" - - By("preparing test repo") - SuiteData.InitTestRepo(ctx, repoDirname, fixtureRelPath) - - By("building images with SBOM") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - buildOut := werfProject.Build(ctx, nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - }) - - Describe("lightweight", Label("tag"), func() { - DescribeTable("should succeed with registry-only SBOM generation (tag)", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - repoDirname := "repo0-tag" - - By("preparing test repo") - SuiteData.InitTestRepo(ctx, repoDirname, "state0") - - By("building images with SBOM") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - buildOut := werfProject.Build(ctx, nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - }) - - Describe("lightweight", Label("digest"), func() { - DescribeTable("should succeed with registry-only SBOM generation (digest)", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - repoDirname := "repo0-digest" - - By("preparing test repo") - SuiteData.InitTestRepo(ctx, repoDirname, "state0") - - By("building images with SBOM") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - buildOut := werfProject.Build(ctx, nil) - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with local repo using Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - Entry("with local repo using BuildKit Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "buildkit-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with rootless isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-rootless", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - XEntry("with local repo using Native Buildah with chroot isolation", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "native-chroot", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - }) - - Describe("negative cases", Label("negative"), func() { - DescribeTable("should fail with mutually exclusive flags", - func(ctx SpecContext, extraArgs []string) { - setupEnv(setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - }) - - repoDirname := "repo0-neg" - SuiteData.InitTestRepo(ctx, repoDirname, "state0") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - - werfProject.SbomGet(ctx, &werf.SbomGetOptions{ - CommonOptions: werf.CommonOptions{ - ShouldFail: true, - ExtraArgs: extraArgs, - }, - }) - }, - Entry("--tag and --digest together", - []string{ - "--tag", "some-tag", - "--digest", "sha256:abc123", - "--repo", "localhost:5000/test", - }, - ), - ) - }) -}) diff --git a/test/e2e/sbom/merge_test.go b/test/e2e/sbom/merge_test.go deleted file mode 100644 index e8453fe0a0..0000000000 --- a/test/e2e/sbom/merge_test.go +++ /dev/null @@ -1,138 +0,0 @@ -package e2e_build_test - -import ( - "encoding/json" - "os" - "path/filepath" - - . "github.com/onsi/ginkgo/v2" - . "github.com/onsi/gomega" - - "github.com/werf/werf/v2/test/pkg/report" - "github.com/werf/werf/v2/test/pkg/werf" -) - -var _ = Describe("Sbom merge", Label("e2e", "sbom", "merge", "simple"), func() { - Describe("happy path", Label("simple"), func() { - DescribeTable("should succeed with registry-only SBOM generation", - func(ctx SpecContext, testOpts simpleTestOptions) { - By("initializing") - setupEnv(testOpts.setupEnvOptions) - - repoDirname := "repo1" - buildReportPath := filepath.Join(SuiteData.TmpDir, "merge-build-report.json") - - By("preparing test repo") - SuiteData.InitTestRepo(ctx, repoDirname, "state1") - - By("building images with SBOM") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - reportProject := report.NewProjectWithReport(werfProject) - buildOut, _ := reportProject.BuildWithReport(ctx, buildReportPath, nil) - - Expect(buildOut).To(ContainSubstring(sbomProcessingPrefix)) - }, - Entry("with Vanilla Docker", simpleTestOptions{setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - WithStagedDockerfileBuilder: false, - }}), - ) - }) - - Describe("negative cases", Label("negative"), func() { - DescribeTable("should fail with invalid input", - func(ctx SpecContext, extraArgs []string) { - setupEnv(setupEnvOptions{ - ContainerBackendMode: "vanilla-docker", - WithLocalRepo: true, - }) - repoDirname := "repo1-neg" - SuiteData.InitTestRepo(ctx, repoDirname, "state1") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - - werfProject.SbomMerge(ctx, &werf.SbomMergeOptions{ - CommonOptions: werf.CommonOptions{ - ShouldFail: true, - ExtraArgs: extraArgs, - }, - }) - }, - Entry("no flags at all", []string{}), - Entry("invalid ispras-format", - []string{ - "--input", "/dev/null", - "--repo", "localhost:5000/test", - "--ispras-format", "invalid-format", - "--app-name", "app", - "--app-version", "1.0.0", - "--manufacturer", "Corp", - }, - ), - Entry("non-existent mapping file", - []string{ - "--input", "/does/not/exist/mapping.json", - "--repo", "localhost:5000/test", - "--ispras-format", "oss", - "--app-name", "app", - "--app-version", "1.0.0", - "--manufacturer", "Corp", - }, - ), - ) - - It("fails on empty mapping JSON", func(ctx SpecContext) { - setupEnv(setupEnvOptions{ContainerBackendMode: "vanilla-docker", WithLocalRepo: true}) - repoDirname := "repo1-neg-empty" - SuiteData.InitTestRepo(ctx, repoDirname, "state1") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - - mappingPath := filepath.Join(GinkgoT().TempDir(), "empty-mapping.json") - writeMergeJSON(mappingPath, map[string]string{}) - - werfProject.SbomMerge(ctx, &werf.SbomMergeOptions{ - CommonOptions: werf.CommonOptions{ - ShouldFail: true, - ExtraArgs: []string{ - "--input", mappingPath, - "--repo", os.Getenv("WERF_REPO"), - "--ispras-format", "oss", - "--app-name", "app", - "--app-version", "1.0.0", - "--manufacturer", "Corp", - }, - }, - }) - }) - - It("fails on invalid digest in mapping", func(ctx SpecContext) { - setupEnv(setupEnvOptions{ContainerBackendMode: "vanilla-docker", WithLocalRepo: true}) - repoDirname := "repo1-neg-digest" - SuiteData.InitTestRepo(ctx, repoDirname, "state1") - werfProject := werf.NewProject(SuiteData.WerfBinPath, SuiteData.GetTestRepoPath(repoDirname)) - - mappingPath := filepath.Join(GinkgoT().TempDir(), "bad-digest-mapping.json") - writeMergeJSON(mappingPath, map[string]string{"frontend": "not-a-valid-digest"}) - - werfProject.SbomMerge(ctx, &werf.SbomMergeOptions{ - CommonOptions: werf.CommonOptions{ - ShouldFail: true, - ExtraArgs: []string{ - "--input", mappingPath, - "--repo", os.Getenv("WERF_REPO"), - "--ispras-format", "oss", - "--app-name", "app", - "--app-version", "1.0.0", - "--manufacturer", "Corp", - }, - }, - }) - }) - }) -}) - -func writeMergeJSON(path string, v any) { - data, err := json.MarshalIndent(v, "", " ") - Expect(err).NotTo(HaveOccurred()) - Expect(os.WriteFile(path, data, 0o644)).To(Succeed()) -}