From 0c0c9a0030e84939077b0f180aac794c34f145bc Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 16 Nov 2022 14:22:33 +0000 Subject: [PATCH 1/5] chore: sort buildOptions alphabetically Signed-off-by: Justin Chadwell --- commands/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/build.go b/commands/build.go index 094224a4a75..2418e9628ce 100644 --- a/commands/build.go +++ b/commands/build.go @@ -60,6 +60,7 @@ type buildOptions struct { contexts []string extraHosts []string imageIDFile string + invoke string labels []string networkMode string noCacheFilter []string @@ -72,7 +73,6 @@ type buildOptions struct { tags []string target string ulimits *dockeropts.UlimitOpt - invoke string commonOptions } From f8b673eccd491f2a66d17baaa968da6155c1e91b Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 16 Nov 2022 15:45:37 +0000 Subject: [PATCH 2/5] build: pass attestation attributes to build request Signed-off-by: Justin Chadwell --- build/build.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build/build.go b/build/build.go index 4143bc0d966..b2e6e883630 100644 --- a/build/build.go +++ b/build/build.go @@ -1109,7 +1109,7 @@ func BuildWithResultHandler(ctx context.Context, nodes []builder.Node, opt map[s FrontendInputs: frontendInputs, } so.Frontend = "" - so.FrontendAttrs = nil + so.FrontendAttrs = attestations.Filter(so.FrontendAttrs) so.FrontendInputs = nil ch, done := progress.NewChannel(pw) From f0262dd10edc2a3fe584f362732c43b4ca2fd14e Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 7 Dec 2022 18:43:36 +0000 Subject: [PATCH 3/5] build: add attestations to build options Signed-off-by: Justin Chadwell --- build/build.go | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/build/build.go b/build/build.go index b2e6e883630..ada2e458927 100644 --- a/build/build.go +++ b/build/build.go @@ -37,6 +37,7 @@ import ( "github.com/moby/buildkit/client" "github.com/moby/buildkit/client/llb" "github.com/moby/buildkit/exporter/containerimage/exptypes" + "github.com/moby/buildkit/frontend/attestations" gateway "github.com/moby/buildkit/frontend/gateway/client" "github.com/moby/buildkit/session" "github.com/moby/buildkit/session/upload/uploadprovider" @@ -67,6 +68,7 @@ type Options struct { Inputs Inputs Allow []entitlements.Entitlement + Attests map[string]*string BuildArgs map[string]string CacheFrom []client.CacheOptionsEntry CacheTo []client.CacheOptionsEntry @@ -578,6 +580,21 @@ func toSolveOpt(ctx context.Context, node builder.Node, multiDriver bool, opt Op } } + if len(opt.Attests) > 0 { + if !bopts.LLBCaps.Contains(apicaps.CapID("exporter.image.attestations")) { + return nil, nil, errors.Errorf("attestations are not supported by the current buildkitd") + } + for k, v := range opt.Attests { + if v == nil { + continue + } + so.FrontendAttrs[k] = *v + } + } + if _, ok := opt.Attests["attest:provenance"]; !ok { + so.FrontendAttrs["attest:provenance"] = "mode=min,inline-only=true" + } + // set platforms if len(opt.Platforms) != 0 { pp := make([]string, len(opt.Platforms)) From b270a202748ba277d0526a918473bb7474963411 Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 7 Dec 2022 18:44:11 +0000 Subject: [PATCH 4/5] build: add attests flag and sbom/provenance shorthands Signed-off-by: Justin Chadwell --- commands/build.go | 21 +++++++++- docs/reference/buildx_build.md | 3 ++ util/buildflags/attests.go | 76 ++++++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 util/buildflags/attests.go diff --git a/commands/build.go b/commands/build.go index 2418e9628ce..3114a0000ae 100644 --- a/commands/build.go +++ b/commands/build.go @@ -53,6 +53,7 @@ type buildOptions struct { printFunc string allow []string + attests []string buildArgs []string cacheFrom []string cacheTo []string @@ -85,6 +86,9 @@ type commonOptions struct { exportPush bool exportLoad bool + + sbom string + provenance string } func runBuild(dockerCli command.Cli, in buildOptions) (err error) { @@ -212,9 +216,20 @@ func runBuild(dockerCli command.Cli, in buildOptions) (err error) { } } } - opts.Exports = outputs + inAttests := append([]string{}, in.attests...) + if in.provenance != "" { + inAttests = append(inAttests, buildflags.CanonicalizeAttest("provenance", in.provenance)) + } + if in.sbom != "" { + inAttests = append(inAttests, buildflags.CanonicalizeAttest("sbom", in.sbom)) + } + opts.Attests, err = buildflags.ParseAttests(inAttests) + if err != nil { + return err + } + cacheImports, err := buildflags.ParseCacheEntry(in.cacheFrom) if err != nil { return err @@ -504,6 +519,10 @@ func buildCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { flags.Var(options.ulimits, "ulimit", "Ulimit options") + flags.StringArrayVar(&options.attests, "attest", []string{}, `Attestation parameters (format: "type=sbom,generator=image")`) + flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--attest=type=sbom"`) + flags.StringVar(&options.provenance, "provenance", "", `Shortand for "--attest=type=provenance"`) + if isExperimental() { flags.StringVar(&options.invoke, "invoke", "", "Invoke a command after the build [experimental]") } diff --git a/docs/reference/buildx_build.md b/docs/reference/buildx_build.md index ebc02a4dd7b..020e4535c99 100644 --- a/docs/reference/buildx_build.md +++ b/docs/reference/buildx_build.md @@ -17,6 +17,7 @@ Start a build | --- | --- | --- | --- | | [`--add-host`](https://docs.docker.com/engine/reference/commandline/build/#add-entries-to-container-hosts-file---add-host) | `stringSlice` | | Add a custom host-to-IP mapping (format: `host:ip`) | | [`--allow`](#allow) | `stringSlice` | | Allow extra privileged entitlement (e.g., `network.host`, `security.insecure`) | +| `--attest` | `stringArray` | | Attestation parameters (format: `type=sbom,generator=image`) | | [`--build-arg`](#build-arg) | `stringArray` | | Set build-time variables | | [`--build-context`](#build-context) | `stringArray` | | Additional build contexts (e.g., name=path) | | [`--builder`](#builder) | `string` | | Override the configured builder instance | @@ -36,9 +37,11 @@ Start a build | [`--platform`](#platform) | `stringArray` | | Set target platform for build | | `--print` | `string` | | Print result of information request (e.g., outline, targets) [experimental] | | [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output | +| `--provenance` | `string` | | Shortand for `--attest=type=provenance` | | `--pull` | | | Always attempt to pull all referenced images | | [`--push`](#push) | | | Shorthand for `--output=type=registry` | | `-q`, `--quiet` | | | Suppress the build output and print image ID on success | +| `--sbom` | `string` | | Shorthand for `--attest=type=sbom` | | [`--secret`](#secret) | `stringArray` | | Secret to expose to the build (format: `id=mysecret[,src=/local/secret]`) | | [`--shm-size`](#shm-size) | `bytes` | `0` | Size of `/dev/shm` | | [`--ssh`](#ssh) | `stringArray` | | SSH agent socket or keys to expose to the build (format: `default\|[=\|[,]]`) | diff --git a/util/buildflags/attests.go b/util/buildflags/attests.go new file mode 100644 index 00000000000..b9aad1a2e8b --- /dev/null +++ b/util/buildflags/attests.go @@ -0,0 +1,76 @@ +package buildflags + +import ( + "encoding/csv" + "fmt" + "strconv" + "strings" + + "github.com/pkg/errors" +) + +func CanonicalizeAttest(attestType string, in string) string { + if in == "" { + return "" + } + if b, err := strconv.ParseBool(in); err == nil { + return fmt.Sprintf("type=%s,enabled=%t", attestType, b) + } + return fmt.Sprintf("type=%s,%s", attestType, in) +} + +func ParseAttests(in []string) (map[string]*string, error) { + out := map[string]*string{} + for _, in := range in { + in := in + attestType, enabled, err := parseAttest(in) + if err != nil { + return nil, err + } + + k := "attest:" + attestType + if enabled { + out[k] = &in + } else { + out[k] = nil + } + } + return out, nil +} + +func parseAttest(in string) (string, bool, error) { + if in == "" { + return "", false, nil + } + + csvReader := csv.NewReader(strings.NewReader(in)) + fields, err := csvReader.Read() + if err != nil { + return "", false, err + } + + attestType := "" + enabled := true + for _, field := range fields { + key, value, ok := strings.Cut(field, "=") + if !ok { + return "", false, errors.Errorf("invalid value %s", field) + } + key = strings.TrimSpace(strings.ToLower(key)) + + switch key { + case "type": + attestType = value + case "enabled": + enabled, err = strconv.ParseBool(value) + if err != nil { + return "", false, err + } + } + } + if attestType == "" { + return "", false, errors.Errorf("attestation type not specified") + } + + return attestType, enabled, nil +} From 25aa893bad0e4e62ee9e7df575b6e79cef640ebd Mon Sep 17 00:00:00 2001 From: Justin Chadwell Date: Wed, 7 Dec 2022 18:44:21 +0000 Subject: [PATCH 5/5] bake: add attests field Signed-off-by: Justin Chadwell --- bake/bake.go | 15 ++++++++++++++- commands/bake.go | 9 +++++++++ docs/reference/buildx_bake.md | 2 ++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/bake/bake.go b/bake/bake.go index 3ba80d2d312..cec4aaee072 100644 --- a/bake/bake.go +++ b/bake/bake.go @@ -417,7 +417,7 @@ func (c Config) newOverrides(v []string) (map[string]map[string]Override, error) o := t[kk[1]] switch keys[1] { - case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh": + case "output", "cache-to", "cache-from", "tags", "platform", "secrets", "ssh", "attest": if len(parts) == 2 { o.ArrValue = append(o.ArrValue, parts[1]) } @@ -558,6 +558,7 @@ type Target struct { // Inherits is the only field that cannot be overridden with --set Inherits []string `json:"inherits,omitempty" hcl:"inherits,optional"` + Attest []string `json:"attest,omitempty" hcl:"attest,optional"` Context *string `json:"context,omitempty" hcl:"context,optional"` Contexts map[string]string `json:"contexts,omitempty" hcl:"contexts,optional"` Dockerfile *string `json:"dockerfile,omitempty" hcl:"dockerfile,optional"` @@ -583,6 +584,7 @@ type Target struct { } func (t *Target) normalize() { + t.Attest = removeDupes(t.Attest) t.Tags = removeDupes(t.Tags) t.Secrets = removeDupes(t.Secrets) t.SSH = removeDupes(t.SSH) @@ -636,6 +638,9 @@ func (t *Target) Merge(t2 *Target) { if t2.Target != nil { t.Target = t2.Target } + if t2.Attest != nil { // merge + t.Attest = append(t.Attest, t2.Attest...) + } if t2.Secrets != nil { // merge t.Secrets = append(t.Secrets, t2.Secrets...) } @@ -718,6 +723,8 @@ func (t *Target) AddOverrides(overrides map[string]Override) error { t.Platforms = o.ArrValue case "output": t.Outputs = o.ArrValue + case "attest": + t.Attest = append(t.Attest, o.ArrValue...) case "no-cache": noCache, err := strconv.ParseBool(value) if err != nil { @@ -971,6 +978,12 @@ func toBuildOpt(t *Target, inp *Input) (*build.Options, error) { } bo.Exports = outputs + attests, err := buildflags.ParseAttests(t.Attest) + if err != nil { + return nil, err + } + bo.Attests = attests + return bo, nil } diff --git a/commands/bake.go b/commands/bake.go index a55d800ed23..4c7ffc4f892 100644 --- a/commands/bake.go +++ b/commands/bake.go @@ -10,6 +10,7 @@ import ( "github.com/docker/buildx/bake" "github.com/docker/buildx/build" "github.com/docker/buildx/builder" + "github.com/docker/buildx/util/buildflags" "github.com/docker/buildx/util/confutil" "github.com/docker/buildx/util/dockerutil" "github.com/docker/buildx/util/progress" @@ -73,6 +74,12 @@ func runBake(dockerCli command.Cli, targets []string, in bakeOptions) (err error if in.pull != nil { overrides = append(overrides, fmt.Sprintf("*.pull=%t", *in.pull)) } + if in.sbom != "" { + overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("sbom", in.sbom))) + } + if in.provenance != "" { + overrides = append(overrides, fmt.Sprintf("*.attest=%s", buildflags.CanonicalizeAttest("provenance", in.provenance))) + } contextPathHash, _ := os.Getwd() ctx2, cancel := context.WithCancel(context.TODO()) @@ -199,6 +206,8 @@ func bakeCmd(dockerCli command.Cli, rootOpts *rootOptions) *cobra.Command { flags.BoolVar(&options.exportLoad, "load", false, `Shorthand for "--set=*.output=type=docker"`) flags.BoolVar(&options.printOnly, "print", false, "Print the options without building") flags.BoolVar(&options.exportPush, "push", false, `Shorthand for "--set=*.output=type=registry"`) + flags.StringVar(&options.sbom, "sbom", "", `Shorthand for "--set=*.attest=type=sbom"`) + flags.StringVar(&options.provenance, "provenance", "", `Shorthand for "--set=*.attest=type=provenance"`) flags.StringArrayVar(&options.overrides, "set", nil, `Override target value (e.g., "targetpattern.key=value")`) commonBuildFlags(&options.commonOptions, flags) diff --git a/docs/reference/buildx_bake.md b/docs/reference/buildx_bake.md index 73d1799b77b..d74a8ca04e3 100644 --- a/docs/reference/buildx_bake.md +++ b/docs/reference/buildx_bake.md @@ -22,8 +22,10 @@ Build from a file | [`--no-cache`](#no-cache) | | | Do not use cache when building the image | | [`--print`](#print) | | | Print the options without building | | [`--progress`](#progress) | `string` | `auto` | Set type of progress output (`auto`, `plain`, `tty`). Use plain to show container output | +| `--provenance` | `string` | | Shorthand for `--set=*.attest=type=provenance` | | [`--pull`](#pull) | | | Always attempt to pull all referenced images | | `--push` | | | Shorthand for `--set=*.output=type=registry` | +| `--sbom` | `string` | | Shorthand for `--set=*.attest=type=sbom` | | [`--set`](#set) | `stringArray` | | Override target value (e.g., `targetpattern.key=value`) |