Browse Source

introduce build.provenance and sbom support

Signed-off-by: Nicolas De Loof <[email protected]>
Nicolas De Loof 4 months ago
parent
commit
8f91793fb5
7 changed files with 63 additions and 9 deletions
  1. 6 2
      cmd/compose/build.go
  2. 2 0
      docs/reference/compose_build.md
  3. 18 0
      docs/reference/docker_compose_build.yaml
  4. 2 1
      go.mod
  5. 4 2
      go.sum
  6. 6 2
      pkg/api/api.go
  7. 25 2
      pkg/compose/build.go

+ 6 - 2
cmd/compose/build.go

@@ -45,7 +45,8 @@ type buildOptions struct {
 	deps       bool
 	print      bool
 	check      bool
-	provenance bool
+	sbom       string
+	provenance string
 }
 
 func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions, error) {
@@ -84,6 +85,7 @@ func (opts buildOptions) toAPIBuildOptions(services []string) (api.BuildOptions,
 		Check:      opts.check,
 		SSHs:       SSHKeys,
 		Builder:    builderName,
+		SBOM:       opts.sbom,
 		Provenance: opts.provenance,
 	}, nil
 }
@@ -125,6 +127,8 @@ func buildCommand(p *ProjectOptions, dockerCli command.Cli, backend api.Service)
 	flags.StringVar(&opts.ssh, "ssh", "", "Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent)")
 	flags.StringVar(&opts.builder, "builder", "", "Set builder to use")
 	flags.BoolVar(&opts.deps, "with-dependencies", false, "Also build dependencies (transitively)")
+	flags.StringVar(&opts.provenance, "provenance", "", `Add a provenance attestation`)
+	flags.StringVar(&opts.sbom, "sbom", "", `Add a SBOM attestation`)
 
 	flags.Bool("parallel", true, "Build images in parallel. DEPRECATED")
 	flags.MarkHidden("parallel") //nolint:errcheck
@@ -156,7 +160,7 @@ func runBuild(ctx context.Context, dockerCli command.Cli, backend api.Service, o
 	}
 
 	apiBuildOptions, err := opts.toAPIBuildOptions(services)
-	apiBuildOptions.Provenance = true
+	apiBuildOptions.Attestations = true
 	if err != nil {
 		return err
 	}

+ 2 - 0
docs/reference/compose_build.md

@@ -22,9 +22,11 @@ run `docker compose build` to rebuild it.
 | `-m`, `--memory`      | `bytes`       | `0`     | Set memory limit for the build container. Not supported by BuildKit.                                        |
 | `--no-cache`          | `bool`        |         | Do not use cache when building the image                                                                    |
 | `--print`             | `bool`        |         | Print equivalent bake file                                                                                  |
+| `--provenance`        | `string`      |         | Add a provenance attestation                                                                                |
 | `--pull`              | `bool`        |         | Always attempt to pull a newer version of the image                                                         |
 | `--push`              | `bool`        |         | Push service images                                                                                         |
 | `-q`, `--quiet`       | `bool`        |         | Don't print anything to STDOUT                                                                              |
+| `--sbom`              | `string`      |         | Add a SBOM attestation                                                                                      |
 | `--ssh`               | `string`      |         | Set SSH authentications used when building service images. (use 'default' for using your default SSH Agent) |
 | `--with-dependencies` | `bool`        |         | Also build dependencies (transitively)                                                                      |
 

+ 18 - 0
docs/reference/docker_compose_build.yaml

@@ -125,6 +125,15 @@ options:
       experimentalcli: false
       kubernetes: false
       swarm: false
+    - option: provenance
+      value_type: string
+      description: Add a provenance attestation
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
     - option: pull
       value_type: bool
       default_value: "false"
@@ -156,6 +165,15 @@ options:
       experimentalcli: false
       kubernetes: false
       swarm: false
+    - option: sbom
+      value_type: string
+      description: Add a SBOM attestation
+      deprecated: false
+      hidden: false
+      experimental: false
+      experimentalcli: false
+      kubernetes: false
+      swarm: false
     - option: ssh
       value_type: string
       description: |

+ 2 - 1
go.mod

@@ -8,7 +8,7 @@ require (
 	github.com/Microsoft/go-winio v0.6.2
 	github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
 	github.com/buger/goterm v1.0.4
-	github.com/compose-spec/compose-go/v2 v2.7.1
+	github.com/compose-spec/compose-go/v2 v2.7.2-0.20250721082312-a42e7579d813
 	github.com/containerd/containerd/v2 v2.1.3
 	github.com/containerd/errdefs v1.0.0
 	github.com/containerd/platforms v1.0.0-rc.1
@@ -181,6 +181,7 @@ require (
 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 // indirect
 	go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
 	go.opentelemetry.io/proto/otlp v1.5.0 // indirect
+	go.yaml.in/yaml/v3 v3.0.4 // indirect
 	golang.org/x/crypto v0.37.0 // indirect
 	golang.org/x/net v0.39.0 // indirect
 	golang.org/x/oauth2 v0.29.0 // indirect

+ 4 - 2
go.sum

@@ -80,8 +80,8 @@ github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e
 github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE=
 github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4=
-github.com/compose-spec/compose-go/v2 v2.7.1 h1:EUIbuaD0R/J1KA+FbJMNbcS9+jt/CVudbp5iHqUllSs=
-github.com/compose-spec/compose-go/v2 v2.7.1/go.mod h1:TmjkIB9W73fwVxkYY+u2uhMbMUakjiif79DlYgXsyvU=
+github.com/compose-spec/compose-go/v2 v2.7.2-0.20250721082312-a42e7579d813 h1:Lmtch++VWP8Oqzff+FJflVW3g6/JFtDc3wq+tvRsagE=
+github.com/compose-spec/compose-go/v2 v2.7.2-0.20250721082312-a42e7579d813/go.mod h1:veko/VB7URrg/tKz3vmIAQDaz+CGiXH8vZsW79NmAww=
 github.com/containerd/cgroups/v3 v3.0.5 h1:44na7Ud+VwyE7LIoJ8JTNQOa549a8543BmzaJHo6Bzo=
 github.com/containerd/cgroups/v3 v3.0.5/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins=
 github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc=
@@ -539,6 +539,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
 go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
 go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
+go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
+go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=

+ 6 - 2
pkg/api/api.go

@@ -170,8 +170,12 @@ type BuildOptions struct {
 	Print bool
 	// Check let builder validate build configuration
 	Check bool
-	// Provenance
-	Provenance bool
+	// Attestations allows to enable attestations generation
+	Attestations bool
+	// Provenance generate a provenance attestation
+	Provenance string
+	// SBOM generate a SBOM attestation
+	SBOM string
 }
 
 // Apply mutates project according to build options

+ 25 - 2
pkg/compose/build.go

@@ -21,6 +21,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"strconv"
 	"strings"
 	"time"
 
@@ -397,6 +398,7 @@ func resolveAndMergeBuildArgs(dockerCli command.Cli, project *types.Project, ser
 	return result
 }
 
+//nolint:gocyclo
 func (s *composeService) toBuildOptions(project *types.Project, service types.ServiceConfig, options api.BuildOptions) (build.Options, error) {
 	plats, err := parsePlatforms(service)
 	if err != nil {
@@ -471,8 +473,19 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 	}
 
 	attests := map[string]*string{}
-	if !options.Provenance {
-		attests["provenance"] = nil
+	if options.Attestations {
+		if service.Build.Provenance != "" {
+			attests["provenance"] = attestation(service.Build.Provenance, "provenance")
+		}
+		if service.Build.SBOM != "" {
+			attests["sbom"] = attestation(service.Build.SBOM, "sbom")
+		}
+	}
+	if options.Provenance != "" {
+		attests["provenance"] = attestation(options.Provenance, "provenance")
+	}
+	if options.SBOM != "" {
+		attests["sbom"] = attestation(options.SBOM, "sbom")
 	}
 
 	return build.Options{
@@ -502,6 +515,16 @@ func (s *composeService) toBuildOptions(project *types.Project, service types.Se
 	}, nil
 }
 
+func attestation(attest string, val string) *string {
+	if b, err := strconv.ParseBool(val); err == nil {
+		s := fmt.Sprintf("type=%s,disabled=%t", attest, b)
+		return &s
+	} else {
+		s := fmt.Sprintf("type=%s,%s", attest, val)
+		return &s
+	}
+}
+
 func toUlimitOpt(ulimits map[string]*types.UlimitsConfig) *cliopts.UlimitOpt {
 	ref := map[string]*container.Ulimit{}
 	for _, limit := range toUlimits(ulimits) {