diff --git a/client/client_test.go b/client/client_test.go index 4fed74ad9..e98014f96 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -50,6 +50,7 @@ import ( binfotypes "github.com/moby/buildkit/util/buildinfo/types" "github.com/moby/buildkit/util/contentutil" "github.com/moby/buildkit/util/entitlements" + "github.com/moby/buildkit/util/purl" "github.com/moby/buildkit/util/testutil" "github.com/moby/buildkit/util/testutil/echoserver" "github.com/moby/buildkit/util/testutil/httpserver" @@ -6527,13 +6528,16 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) { return res, nil } - target := registry + "/buildkit/testattestations:latest" + targets := []string{ + registry + "/buildkit/testattestationsfoo:latest", + registry + "/buildkit/testattestationsbar:latest", + } _, err = c.Build(sb.Context(), SolveOpt{ Exports: []ExportEntry{ { Type: ExporterImage, Attrs: map[string]string{ - "name": target, + "name": strings.Join(targets, ","), "push": "true", }, }, @@ -6541,7 +6545,7 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) { }, "", frontend, nil) require.NoError(t, err) - desc, provider, err := contentutil.ProviderFromRef(target) + desc, provider, err := contentutil.ProviderFromRef(targets[0]) require.NoError(t, err) imgs, err := testutil.ReadImages(sb.Context(), provider, desc) @@ -6574,15 +6578,29 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) { var attest intoto.Statement require.NoError(t, json.Unmarshal(att.LayersRaw[0], &attest)) + purls := map[string]string{} + for _, k := range targets { + p, _ := purl.RefToPURL(k, &ps[i]) + purls[k] = p + } + require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type) require.Equal(t, "https://example.com/attestations/v1.0", attest.PredicateType) require.Equal(t, map[string]interface{}{"success": true}, attest.Predicate) - subjects := []intoto.Subject{{ - Name: "_", - Digest: map[string]string{ - "sha256": bases[i].Desc.Digest.Encoded(), + subjects := []intoto.Subject{ + { + Name: purls[targets[0]], + Digest: map[string]string{ + "sha256": bases[i].Desc.Digest.Encoded(), + }, }, - }} + { + Name: purls[targets[1]], + Digest: map[string]string{ + "sha256": bases[i].Desc.Digest.Encoded(), + }, + }, + } require.Equal(t, subjects, attest.Subject) var attest2 intoto.Statement @@ -6609,9 +6627,10 @@ func testExportAttestations(t *testing.T, sb integration.Sandbox) { defer client.Close() ctx := namespaces.WithNamespace(sb.Context(), "buildkit") - err = client.ImageService().Delete(ctx, target, images.SynchronousDelete()) - require.NoError(t, err) - + for _, target := range targets { + err = client.ImageService().Delete(ctx, target, images.SynchronousDelete()) + require.NoError(t, err) + } checkAllReleasable(t, c, sb, true) } @@ -6737,8 +6756,11 @@ func testAttestationDefaultSubject(t *testing.T, sb integration.Sandbox) { require.Equal(t, "https://in-toto.io/Statement/v0.1", attest.Type) require.Equal(t, "https://example.com/attestations/v1.0", attest.PredicateType) require.Equal(t, map[string]interface{}{"success": true}, attest.Predicate) + + name, _ := purl.RefToPURL(target, &ps[0]) + subjects := []intoto.Subject{{ - Name: "_", + Name: name, Digest: map[string]string{ "sha256": bases[i].Desc.Digest.Encoded(), }, diff --git a/exporter/containerimage/writer.go b/exporter/containerimage/writer.go index b96cb9bb0..734d1683b 100644 --- a/exporter/containerimage/writer.go +++ b/exporter/containerimage/writer.go @@ -30,6 +30,7 @@ import ( binfotypes "github.com/moby/buildkit/util/buildinfo/types" "github.com/moby/buildkit/util/compression" "github.com/moby/buildkit/util/progress" + "github.com/moby/buildkit/util/purl" "github.com/moby/buildkit/util/system" "github.com/moby/buildkit/util/tracing" digest "github.com/opencontainers/go-digest" @@ -290,13 +291,6 @@ func (ic *ImageWriter) extractAttestations(ctx context.Context, opts *ImageCommi if len(predicate) == 0 { predicate = nil } - statements[i] = intoto.Statement{ - StatementHeader: intoto.StatementHeader{ - Type: intoto.StatementInTotoV01, - PredicateType: att.InToto.PredicateType, - }, - Predicate: json.RawMessage(predicate), - } if len(att.InToto.Subjects) == 0 { att.InToto.Subjects = []result.InTotoSubject{{ @@ -304,21 +298,50 @@ func (ic *ImageWriter) extractAttestations(ctx context.Context, opts *ImageCommi }} } - statements[i].Subject = make([]intoto.Subject, len(att.InToto.Subjects)) - for j, subject := range att.InToto.Subjects { - statements[i].Subject[j].Name = "_" + subjects := make([]intoto.Subject, 0, len(att.InToto.Subjects)) + for _, subject := range att.InToto.Subjects { + name := "_" if subject.Name != "" { - statements[i].Subject[j].Name = subject.Name + name = subject.Name } switch subject.Kind { case gatewaypb.InTotoSubjectKindSelf: - statements[i].Subject[j].Digest = result.DigestMap(desc.Digest) + var names []string + if opts.ImageName != "" { + for _, name := range strings.Split(opts.ImageName, ",") { + name, err := purl.RefToPURL(name, desc.Platform) + if err != nil { + return err + } + names = append(names, name) + } + } else { + names = []string{name} + } + for _, name := range names { + subjects = append(subjects, intoto.Subject{ + Name: name, + Digest: result.DigestMap(desc.Digest), + }) + } case gatewaypb.InTotoSubjectKindRaw: - statements[i].Subject[j].Digest = result.DigestMap(subject.Digest...) + subjects = append(subjects, intoto.Subject{ + Name: name, + Digest: result.DigestMap(subject.Digest...), + }) + default: return errors.Errorf("unknown attestation subject kind %q", subject.Kind) } } + statements[i] = intoto.Statement{ + StatementHeader: intoto.StatementHeader{ + Type: intoto.StatementInTotoV01, + PredicateType: att.InToto.PredicateType, + Subject: subjects, + }, + Predicate: json.RawMessage(predicate), + } } return nil }) diff --git a/util/purl/image_test.go b/util/purl/image_test.go index 78d0d6809..621de37bf 100644 --- a/util/purl/image_test.go +++ b/util/purl/image_test.go @@ -90,6 +90,7 @@ func TestRefToPURL(t *testing.T) { func TestPURLToRef(t *testing.T) { testDgst := digest.FromBytes([]byte("test")).String() p := platforms.Normalize(platforms.DefaultSpec()) + p.OSVersion = "" // OSVersion is not supported in PURL testPlatform := &p encPlatform := url.QueryEscape(platforms.Format(platforms.Normalize(p)))