diff --git a/bake/bake_test.go b/bake/bake_test.go index 4311b252e8c5..dd62844b0554 100644 --- a/bake/bake_test.go +++ b/bake/bake_test.go @@ -1358,3 +1358,110 @@ func TestJSONNullVars(t *testing.T) { require.NoError(t, err) require.Equal(t, map[string]*string{"bar": ptrstr("baz")}, m["default"].Args) } + +func TestAttestDuplicates(t *testing.T) { + fp := File{ + Name: "docker-bake.hcl", + Data: []byte( + `target "default" { + attest = ["type=sbom", "type=sbom,generator=custom"] + }`), + } + ctx := context.TODO() + m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) + require.NoError(t, err) + + _, err = TargetsToBuildOpt(m, &Input{}) + require.Error(t, err) +} + +func TestAttestOverride(t *testing.T) { + ctx := context.TODO() + + // file without attest set + fp := File{ + Name: "docker-bake.hcl", + Data: []byte(`target "default" {}`), + } + + // no override + m, _, err := ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) + require.NoError(t, err) + require.Empty(t, m["default"].Attest) + + opts, err := TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Empty(t, opts["default"].Attests) + + // with override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,generator=custom"}, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,generator=custom"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": ptrstr("type=sbom,generator=custom")}, opts["default"].Attests) + + // with disabled=true override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,disabled=true"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": nil}, opts["default"].Attests) + + // with disabled=false override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=false"}, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,disabled=false"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": ptrstr("type=sbom,disabled=false")}, opts["default"].Attests) + + // file with attest set + fp = File{ + Name: "docker-bake.hcl", + Data: []byte( + `target "default" { + attest = ["type=sbom,generator=custom"] + }`), + } + + // no override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, nil, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,generator=custom"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": ptrstr("type=sbom,generator=custom")}, opts["default"].Attests) + + // with duplicate override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,generator=custom"}, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,generator=custom"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": ptrstr("type=sbom,generator=custom")}, opts["default"].Attests) + + // with disabled=true override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=true"}, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,generator=custom", "type=sbom,disabled=true"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": nil}, opts["default"].Attests) + + // with disabled=false override + m, _, err = ReadTargets(ctx, []File{fp}, []string{"default"}, []string{"*.attest=type=sbom,disabled=false"}, nil) + require.NoError(t, err) + require.Equal(t, []string{"type=sbom,generator=custom", "type=sbom,disabled=false"}, m["default"].Attest) + + opts, err = TargetsToBuildOpt(m, &Input{}) + require.NoError(t, err) + require.Equal(t, map[string]*string{"attest:sbom": ptrstr("type=sbom,generator=custom")}, opts["default"].Attests) +} diff --git a/util/buildflags/attests.go b/util/buildflags/attests.go index ab1b163ac98b..aef872c994ee 100644 --- a/util/buildflags/attests.go +++ b/util/buildflags/attests.go @@ -14,7 +14,7 @@ func CanonicalizeAttest(attestType string, in string) string { return "" } if b, err := strconv.ParseBool(in); err == nil { - return fmt.Sprintf("type=%s,enabled=%t", attestType, b) + return fmt.Sprintf("type=%s,disabled=%t", attestType, !b) } return fmt.Sprintf("type=%s,%s", attestType, in) } @@ -23,57 +23,65 @@ func ParseAttests(in []string) (map[string]*string, error) { out := map[string]*string{} for _, in := range in { in := in - attestType, enabled, err := parseAttest(in) + attestType, disabled, err := parseAttest(in) if err != nil { return nil, err } k := "attest:" + attestType if _, ok := out[k]; ok { - return nil, errors.Errorf("duplicate attestation field %s", attestType) + if disabled == nil { + return nil, errors.Errorf("duplicate attestation field %s", attestType) + } + if *disabled { + out[k] = nil + } + continue } - if enabled { - out[k] = &in - } else { + + if disabled != nil && *disabled { out[k] = nil + } else { + out[k] = &in } } return out, nil } -func parseAttest(in string) (string, bool, error) { +func parseAttest(in string) (string, *bool, error) { if in == "" { - return "", false, nil + return "", nil, nil } csvReader := csv.NewReader(strings.NewReader(in)) fields, err := csvReader.Read() if err != nil { - return "", false, err + return "", nil, err } attestType := "" - enabled := true + var disabled *bool for _, field := range fields { key, value, ok := strings.Cut(field, "=") if !ok { - return "", false, errors.Errorf("invalid value %s", field) + return "", nil, 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) + case "disabled": + b, err := strconv.ParseBool(value) if err != nil { - return "", false, err + return "", nil, err } + disabled = &b } } if attestType == "" { - return "", false, errors.Errorf("attestation type not specified") + return "", nil, errors.Errorf("attestation type not specified") } - return attestType, enabled, nil + return attestType, disabled, nil }