From 86ede05ba0d8b525d59b0e39df2744d2b2db648c Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Mon, 31 Jan 2022 09:50:30 -0500 Subject: [PATCH 1/2] Permit both BOM formats at the same time Starting with lifecycle 0.13.3, it is permitted to have both the old style label-based BOM information and the new style layer-based BOM information. If the buildpack API is 0.6 or older, label-based BOMs only is OK. If the buildpack API is 0.7, you may have both label-based BOM and layer-based BOM or just layer-based BOM. It is permitted to have just label-based BOM, however, that will generate a warning from the lifecycle. Signed-off-by: Daniel Mikusa --- build.go | 12 ------------ build_test.go | 20 ++++++++++++++++---- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/build.go b/build.go index 70b8752..f20165c 100644 --- a/build.go +++ b/build.go @@ -345,12 +345,6 @@ func Build(builder Builder, options ...Option) { } } - // even if there is data, do not write a BOM if we have buildpack API 0.7, that will cause a lifecycle error - if API == "0.7" && len(launch.BOM) > 0 { - logger.Info("Warning: this buildpack is including both old and new format SBOM information, which is an invalid state. To prevent the lifecycle from failing, libcnb is discarding the old SBOM information.") - launch.BOM = nil - } - if err = config.tomlWriter.Write(file, launch); err != nil { config.exitHandler.Error(fmt.Errorf("unable to write application metadata %s\n%w", file, err)) return @@ -366,12 +360,6 @@ func Build(builder Builder, options ...Option) { file = filepath.Join(ctx.Layers.Path, "build.toml") logger.Debugf("Writing build metadata: %s <= %+v", file, build) - // even if there is data, do not write a BOM if we have buildpack API 0.7, that will cause a lifecycle error - if API == "0.7" && len(build.BOM) > 0 { - logger.Info("Warning: this buildpack is including both old and new format SBOM information, which is an invalid state. To prevent the lifecycle from failing, libcnb is discarding the old SBOM information.") - build.BOM = nil - } - if err = config.tomlWriter.Write(file, build); err != nil { config.exitHandler.Error(fmt.Errorf("unable to write build metadata %s\n%w", file, err)) return diff --git a/build_test.go b/build_test.go index 36cfe75..ffcde25 100644 --- a/build_test.go +++ b/build_test.go @@ -640,7 +640,7 @@ version = "1.1.1" Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed()) }) - it("writes launch.toml with BOM entries which are removed", func() { + it("writes launch.toml with BOM entries which are still permitted", func() { builder.On("Build", mock.Anything).Return(libcnb.BuildResult{ BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{ { @@ -676,11 +676,17 @@ version = "1.1.1" Default: true, }, }, - BOM: nil, + BOM: []libcnb.BOMEntry{ + { + Name: "test-launch-bom-entry", + Metadata: map[string]interface{}{"test-key": "test-value"}, + Launch: true, + }, + }, })) }) - it("writes build.toml with BOM entries which are removed", func() { + it("writes build.toml with BOM entries which are still permitted", func() { builder.On("Build", mock.Anything).Return(libcnb.BuildResult{ BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{ { @@ -708,7 +714,13 @@ version = "1.1.1" Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "build.toml"))) Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.BuildTOML{ - BOM: nil, + BOM: []libcnb.BOMEntry{ + { + Name: "test-build-bom-entry", + Metadata: map[string]interface{}{"test-key": "test-value"}, + Build: true, + }, + }, Unmet: []libcnb.UnmetPlanEntry{ { Name: "test-entry", From f496f6e88d4daeebd2603d2d5b175a77b57a23b4 Mon Sep 17 00:00:00 2001 From: Daniel Mikusa Date: Mon, 31 Jan 2022 12:45:49 -0500 Subject: [PATCH 2/2] Adds WithBOMLabel Config Option to control writing BOM Labels In some environments and with some applications, a long enough BOM label may be generated that it will either break Kubernetes or cause your application to fail to start. With the changes in lifecycle 0.13.3, we are enabling the BOM label again, but due to the issue above we also need a way for users to disable it if there are problems. When running `libcnb.Build` you may now include an `Option` of `WithBOMLabel` that has an argument of either true or false. If not set, it defaults to false. This controls the output of libcnb.BOMEntry items in `launch.toml` and `build.toml`. If true, BOM entries are written. If false, BOM entries are not written, even if they are returned by the buildpack. Signed-off-by: Daniel Mikusa --- build.go | 3 ++- build_test.go | 48 +++++++++++++++++++++++++++++++----------------- config.go | 10 ++++++++++ 3 files changed, 43 insertions(+), 18 deletions(-) diff --git a/build.go b/build.go index f20165c..fb5199d 100644 --- a/build.go +++ b/build.go @@ -122,6 +122,7 @@ type Builder interface { func Build(builder Builder, options ...Option) { config := Config{ arguments: os.Args, + bomLabel: false, environmentWriter: internal.EnvironmentWriter{}, exitHandler: internal.NewExitHandler(), tomlWriter: internal.TOMLWriter{}, @@ -315,7 +316,7 @@ func Build(builder Builder, options ...Option) { // Deprecated: as of Buildpack API 0.7, to be removed in a future version var launchBOM, buildBOM []BOMEntry - if result.BOM != nil { + if result.BOM != nil && config.bomLabel { for _, entry := range result.BOM.Entries { if entry.Launch { launchBOM = append(launchBOM, entry) diff --git a/build_test.go b/build_test.go index ffcde25..b4fd36b 100644 --- a/build_test.go +++ b/build_test.go @@ -198,6 +198,7 @@ version = "1.1.1" it("fails", func() { libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -212,6 +213,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath}), libcnb.WithExitHandler(exitHandler), ) @@ -224,6 +226,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -235,6 +238,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), ) @@ -297,6 +301,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{filepath.Join(buildpackPath, commandPath), layersPath, platformPath, buildpackPlanPath}), ) @@ -309,6 +314,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), fmt.Errorf("test-error")) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -323,6 +329,7 @@ version = "1.1.1" Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -339,6 +346,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(result, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithEnvironmentWriter(environmentWriter), ) @@ -356,6 +364,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(result, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithEnvironmentWriter(environmentWriter), ) @@ -373,6 +382,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(result, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithEnvironmentWriter(environmentWriter), @@ -391,6 +401,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(result, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithEnvironmentWriter(environmentWriter), ) @@ -422,6 +433,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(result, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -453,6 +465,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(result, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -501,6 +514,7 @@ version = "1.1.1" }, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -541,6 +555,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.BuildResult{PersistentMetadata: m}, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -553,6 +568,7 @@ version = "1.1.1" builder.On("Build", mock.Anything).Return(libcnb.NewBuildResult(), nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -573,6 +589,7 @@ version = "1.1.1" Return(libcnb.BuildResult{Layers: []libcnb.LayerContributor{layerContributor}}, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -605,6 +622,7 @@ version = "1.1.1" }, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -626,7 +644,7 @@ version = "1.1.1" })) }) - context("API 0.7", func() { + context("Config bomLabel is false", func() { it.Before(func() { var err error @@ -640,7 +658,7 @@ version = "1.1.1" Expect(ioutil.WriteFile(filepath.Join(buildpackPath, "buildpack.toml"), b.Bytes(), 0600)).To(Succeed()) }) - it("writes launch.toml with BOM entries which are still permitted", func() { + it("writes launch.toml without BOM entries", func() { builder.On("Build", mock.Anything).Return(libcnb.BuildResult{ BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{ { @@ -663,6 +681,7 @@ version = "1.1.1" }, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(false), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) @@ -676,17 +695,11 @@ version = "1.1.1" Default: true, }, }, - BOM: []libcnb.BOMEntry{ - { - Name: "test-launch-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Launch: true, - }, - }, + BOM: nil, })) }) - it("writes build.toml with BOM entries which are still permitted", func() { + it("writes build.toml without BOM entries", func() { builder.On("Build", mock.Anything).Return(libcnb.BuildResult{ BOM: &libcnb.BOM{Entries: []libcnb.BOMEntry{ { @@ -708,19 +721,14 @@ version = "1.1.1" }, nil) libcnb.Build(builder, + libcnb.WithBOMLabel(false), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithTOMLWriter(tomlWriter), ) Expect(tomlWriter.Calls[0].Arguments[0]).To(Equal(filepath.Join(layersPath, "build.toml"))) Expect(tomlWriter.Calls[0].Arguments[1]).To(Equal(libcnb.BuildTOML{ - BOM: []libcnb.BOMEntry{ - { - Name: "test-build-bom-entry", - Metadata: map[string]interface{}{"test-key": "test-value"}, - Build: true, - }, - }, + BOM: nil, Unmet: []libcnb.UnmetPlanEntry{ { Name: "test-entry", @@ -750,6 +758,7 @@ sbom-formats = ["application/vnd.cyclonedx+json"] it("has no SBOM files", func() { libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -774,6 +783,7 @@ sbom-formats = [] Expect(ioutil.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed()) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -798,6 +808,7 @@ sbom-formats = [] Expect(ioutil.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed()) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -809,6 +820,7 @@ sbom-formats = [] Expect(ioutil.WriteFile(filepath.Join(layersPath, "launch.sbom.spdx.json"), []byte{}, 0600)).To(Succeed()) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -820,6 +832,7 @@ sbom-formats = [] Expect(ioutil.WriteFile(filepath.Join(layersPath, "launch.sbom.cdx.json"), []byte{}, 0600)).To(Succeed()) Expect(ioutil.WriteFile(filepath.Join(layersPath, "layer.sbom.cdx.json"), []byte{}, 0600)).To(Succeed()) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) @@ -831,6 +844,7 @@ sbom-formats = [] Expect(ioutil.WriteFile(filepath.Join(layersPath, "launch.sbom.random.json"), []byte{}, 0600)).To(Succeed()) Expect(ioutil.WriteFile(filepath.Join(layersPath, "layer.sbom.cdx.json"), []byte{}, 0600)).To(Succeed()) libcnb.Build(builder, + libcnb.WithBOMLabel(true), libcnb.WithArguments([]string{commandPath, layersPath, platformPath, buildpackPlanPath}), libcnb.WithExitHandler(exitHandler), ) diff --git a/config.go b/config.go index d3ac37c..8380835 100644 --- a/config.go +++ b/config.go @@ -65,6 +65,7 @@ type ExecDWriter interface { // Config is an object that contains configurable properties for execution. type Config struct { arguments []string + bomLabel bool environmentWriter EnvironmentWriter exitHandler ExitHandler tomlWriter TOMLWriter @@ -113,3 +114,12 @@ func WithExecDWriter(execdWriter ExecDWriter) Option { return config } } + +// WithBOMLabel creates an Option that enables/disables writing the BOM Label +// Deprecated: as of Buildpack API 0.7, to be removed in a future version +func WithBOMLabel(bomLabel bool) Option { + return func(config Config) Config { + config.bomLabel = bomLabel + return config + } +}