Merge pull request #1412 from jedevc/attestations-cli

Attestations from buildx
This commit is contained in:
Justin Chadwell 2022-12-08 17:30:31 +00:00 committed by GitHub
commit 1e72e32ec3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 143 additions and 4 deletions

View File

@ -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
}

View File

@ -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))
@ -1109,7 +1126,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)

View File

@ -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)

View File

@ -53,6 +53,7 @@ type buildOptions struct {
printFunc string
allow []string
attests []string
buildArgs []string
cacheFrom []string
cacheTo []string
@ -60,6 +61,7 @@ type buildOptions struct {
contexts []string
extraHosts []string
imageIDFile string
invoke string
labels []string
networkMode string
noCacheFilter []string
@ -72,7 +74,6 @@ type buildOptions struct {
tags []string
target string
ulimits *dockeropts.UlimitOpt
invoke string
commonOptions
}
@ -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]")
}

View File

@ -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`) |

View File

@ -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\|<id>[=<socket>\|<key>[,<key>]]`) |

View File

@ -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
}