Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 87 additions & 0 deletions client/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
testHTTPResolveMetaReuse,
testHTTPResolveMultiBuild,
testGitResolveMutatedSource,
testImageResolveAttestationChainRequiresNetwork,
}

func TestIntegration(t *testing.T) {
Expand Down Expand Up @@ -12249,6 +12250,92 @@ func testHTTPResolveSourceMetadata(t *testing.T, sb integration.Sandbox) {
require.NoError(t, err)
}

func testImageResolveAttestationChainRequiresNetwork(t *testing.T, sb integration.Sandbox) {
// this test temporarily requires direct registry access as the integration test
// mirroring system does not support mirroring attestation chains yet.
// Support is coming in future buildx release.
ctx := sb.Context()
c, err := New(ctx, sb.Address())
require.NoError(t, err)
defer c.Close()

amd64, err := platforms.Parse("linux/amd64")
require.NoError(t, err)

_, err = c.Build(ctx, SolveOpt{}, "test", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
const rootDigest = "sha256:4e91099af134f4b1f509fecbd1a55981dfff18d8029d6782e28413210ef468b7"
const imageDigest = "sha256:d2f0b39234a66c3d58f24200d3fda9e4e3d2263c09f9b7286a826ab639713047"
const attestationDigest = "sha256:fb4c46b14f52d1bf790f593921c52dddc698fc6780792fb8469fc60efc0e609b"
const sigDigest = "sha256:bd0a6b088440ba9838e8eec79e736128fe52afce934578eae30ca8675f6d3142"

id := "registry-1-stage.docker.io/docker/github-builder-test@" + rootDigest
md, err := c.ResolveSourceMetadata(ctx, &pb.SourceOp{
Identifier: "docker-image://" + id,
}, sourceresolver.Opt{
ImageOpt: &sourceresolver.ResolveImageOpt{
NoConfig: true,
AttestationChain: true,
Platform: &amd64,
},
})
if err != nil {
return nil, err
}
require.Equal(t, rootDigest, md.Image.Digest.String())
require.Nil(t, md.Image.Config)
require.NotNil(t, md.Image)
require.NotNil(t, md.Image.AttestationChain)
ac := md.Image.AttestationChain
require.Equal(t, rootDigest, ac.Root.String())
require.Equal(t, imageDigest, ac.ImageManifest.String())
require.Equal(t, attestationDigest, ac.AttestationManifest.String())
require.Len(t, ac.SignatureManifests, 1)
require.Equal(t, sigDigest, ac.SignatureManifests[0].String())

desc := ac.Blobs[ac.Root]
require.Equal(t, rootDigest, desc.Descriptor.Digest.String())
require.Len(t, desc.Data, int(desc.Descriptor.Size))
require.Equal(t, ocispecs.MediaTypeImageIndex, desc.Descriptor.MediaType)
chk := digest.FromBytes(desc.Data)
require.Equal(t, rootDigest, chk.String())

desc = ac.Blobs[ac.ImageManifest]
// image manifest is expected to be missing as content is not needed to verify attestation chain
_, ok := ac.Blobs[digest.Digest(imageDigest)]
require.False(t, ok)

desc = ac.Blobs[ac.AttestationManifest]
require.Equal(t, attestationDigest, desc.Descriptor.Digest.String())
require.Equal(t, ocispecs.MediaTypeImageManifest, desc.Descriptor.MediaType)
require.Len(t, desc.Data, int(desc.Descriptor.Size))
chk = digest.FromBytes(desc.Data)
require.Equal(t, attestationDigest, chk.String())

desc = ac.Blobs[ac.SignatureManifests[0]]
require.Equal(t, sigDigest, desc.Descriptor.Digest.String())
require.Equal(t, ocispecs.MediaTypeImageManifest, desc.Descriptor.MediaType)
require.Len(t, desc.Data, int(desc.Descriptor.Size))
chk = digest.FromBytes(desc.Data)
require.Equal(t, sigDigest, chk.String())

var sigMfst ocispecs.Manifest
err = json.Unmarshal(ac.Blobs[ac.SignatureManifests[0]].Data, &sigMfst)
require.NoError(t, err)
require.Equal(t, 1, len(sigMfst.Layers))
sigLayer := sigMfst.Layers[0]
sigDesc := ac.Blobs[digest.Digest(sigLayer.Digest)]
require.Equal(t, sigLayer.Digest, sigDesc.Descriptor.Digest)
require.Len(t, sigDesc.Data, int(sigDesc.Descriptor.Size))
require.Equal(t, "application/vnd.dev.sigstore.bundle.v0.3+json", sigDesc.Descriptor.MediaType)
chk = digest.FromBytes(sigDesc.Data)
require.Equal(t, sigLayer.Digest, chk)

require.Len(t, md.Image.AttestationChain.Blobs, 4)
return nil, nil
}, nil)
require.NoError(t, err)
}

func testHTTPPruneAfterCacheKey(t *testing.T, sb integration.Sandbox) {
// this test depends on hitting race condition in internal functions.
// If debugging and expecting failure you can add small sleep in beginning of source/http.Exec() to hit reliably
Expand Down
4 changes: 2 additions & 2 deletions client/llb/imagemetaresolver/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,8 @@ func (imr *imageMetaResolver) ResolveImageConfig(ctx context.Context, ref string
defer imr.locker.Unlock(ref)

platform := imr.platform
if opt.Platform != nil {
platform = opt.Platform
if imgOpt := opt.ImageOpt; imgOpt != nil && imgOpt.Platform != nil {
platform = imgOpt.Platform
}

k := imr.key(ref, platform)
Expand Down
4 changes: 2 additions & 2 deletions client/llb/resolver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ func (r *testResolver) ResolveImageConfig(ctx context.Context, ref string, opt s

img.Config.WorkingDir = r.dir

if opt.Platform != nil {
r.platform = platforms.Format(*opt.Platform)
if imgOpt := opt.ImageOpt; imgOpt != nil && imgOpt.Platform != nil {
r.platform = platforms.Format(*imgOpt.Platform)
}

dt, err := json.Marshal(img)
Expand Down
4 changes: 2 additions & 2 deletions client/llb/source.go
Original file line number Diff line number Diff line change
Expand Up @@ -146,8 +146,8 @@ func Image(ref string, opts ...ImageOption) State {
p = c.Platform
}
_, _, dt, err := info.metaResolver.ResolveImageConfig(ctx, ref, sourceresolver.Opt{
Platform: p,
ImageOpt: &sourceresolver.ResolveImageOpt{
Platform: p,
ResolveMode: info.resolveMode.String(),
},
})
Expand All @@ -163,8 +163,8 @@ func Image(ref string, opts ...ImageOption) State {
p = c.Platform
}
ref, dgst, dt, err := info.metaResolver.ResolveImageConfig(context.TODO(), ref, sourceresolver.Opt{
Platform: p,
ImageOpt: &sourceresolver.ResolveImageOpt{
Platform: p,
ResolveMode: info.resolveMode.String(),
},
})
Expand Down
27 changes: 22 additions & 5 deletions client/llb/sourceresolver/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ type MetaResolver interface {
type Opt struct {
LogName string
SourcePolicies []*spb.Policy
Platform *ocispecs.Platform

ImageOpt *ResolveImageOpt
OCILayoutOpt *ResolveOCILayoutOpt
Expand All @@ -40,12 +39,29 @@ type MetaResponse struct {
}

type ResolveImageOpt struct {
ResolveMode string
Platform *ocispecs.Platform
ResolveMode string
NoConfig bool
AttestationChain bool
}

type ResolveImageResponse struct {
Digest digest.Digest
Config []byte
Digest digest.Digest
Config []byte
AttestationChain *AttestationChain
}

type AttestationChain struct {
Root digest.Digest
ImageManifest digest.Digest
AttestationManifest digest.Digest
SignatureManifests []digest.Digest
Blobs map[digest.Digest]Blob
}

type Blob struct {
Descriptor ocispecs.Descriptor
Data []byte
}

type ResolveGitOpt struct {
Expand All @@ -67,7 +83,8 @@ type ResolveHTTPResponse struct {
}

type ResolveOCILayoutOpt struct {
Store ResolveImageConfigOptStore
Platform *ocispecs.Platform
Store ResolveImageConfigOptStore
}

type ResolveImageConfigOptStore struct {
Expand Down
4 changes: 2 additions & 2 deletions frontend/dockerfile/dockerfile2llb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -541,9 +541,9 @@ func toDispatchState(ctx context.Context, dt []byte, opt ConvertOpt) (*dispatchS
}
prefix += "internal]"
mutRef, dgst, dt, err := metaResolver.ResolveImageConfig(ctx, d.stage.BaseName, sourceresolver.Opt{
LogName: fmt.Sprintf("%s load metadata for %s", prefix, d.stage.BaseName),
Platform: platform,
LogName: fmt.Sprintf("%s load metadata for %s", prefix, d.stage.BaseName),
ImageOpt: &sourceresolver.ResolveImageOpt{
Platform: platform,
ResolveMode: opt.ImageResolveMode.String(),
},
})
Expand Down
8 changes: 4 additions & 4 deletions frontend/dockerui/namedcontext.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,9 @@ func (nc *NamedContext) load(ctx context.Context, count int) (*llb.State, *docke
named = reference.TagNameOnly(named)

ref, dgst, data, err := nc.bc.client.ResolveImageConfig(ctx, named.String(), sourceresolver.Opt{
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, ref),
Platform: opt.Platform,
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, ref),
ImageOpt: &sourceresolver.ResolveImageOpt{
Platform: opt.Platform,
ResolveMode: opt.ResolveMode,
},
})
Expand Down Expand Up @@ -183,9 +183,9 @@ func (nc *NamedContext) load(ctx context.Context, count int) (*llb.State, *docke
}

_, dgst, data, err := nc.bc.client.ResolveImageConfig(ctx, dummyRef.String(), sourceresolver.Opt{
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, dummyRef.String()),
Platform: opt.Platform,
LogName: fmt.Sprintf("[context %s] load metadata for %s", nc.nameWithPlatform, dummyRef.String()),
OCILayoutOpt: &sourceresolver.ResolveOCILayoutOpt{
Platform: opt.Platform,
Store: sourceresolver.ResolveImageConfigOptStore{
SessionID: nc.bc.bopts.SessionID,
StoreID: named.Name(),
Expand Down
42 changes: 40 additions & 2 deletions frontend/gateway/gateway.go
Original file line number Diff line number Diff line change
Expand Up @@ -631,10 +631,17 @@ func (lbf *llbBridgeForwarder) ResolveSourceMeta(ctx context.Context, req *pb.Re
resolveopt := sourceresolver.Opt{
LogName: req.LogName,
SourcePolicies: req.SourcePolicies,
Platform: platform,
}
resolveopt.ImageOpt = &sourceresolver.ResolveImageOpt{
ResolveMode: req.ResolveMode,
Platform: platform,
}
if req.Image != nil {
resolveopt.ImageOpt.NoConfig = req.Image.NoConfig
resolveopt.ImageOpt.AttestationChain = req.Image.AttestationChain
}
resolveopt.OCILayoutOpt = &sourceresolver.ResolveOCILayoutOpt{
Platform: platform,
}
if req.Git != nil {
resolveopt.GitOpt = &sourceresolver.ResolveGitOpt{
Expand All @@ -656,6 +663,9 @@ func (lbf *llbBridgeForwarder) ResolveSourceMeta(ctx context.Context, req *pb.Re
Digest: string(resp.Image.Digest),
Config: resp.Image.Config,
}
if resp.Image.AttestationChain != nil {
r.Image.AttestationChain = toPBAttestationChain(resp.Image.AttestationChain)
}
}
if resp.Git != nil {
r.Git = &pb.ResolveSourceGitResponse{
Expand Down Expand Up @@ -698,18 +708,19 @@ func (lbf *llbBridgeForwarder) ResolveImageConfig(ctx context.Context, req *pb.R
resolveopt := sourceresolver.Opt{
LogName: req.LogName,
SourcePolicies: req.SourcePolicies,
Platform: platform,
}
if sourceresolver.ResolverType(req.ResolverType) == sourceresolver.ResolverTypeRegistry {
resolveopt.ImageOpt = &sourceresolver.ResolveImageOpt{
ResolveMode: req.ResolveMode,
Platform: platform,
}
} else if sourceresolver.ResolverType(req.ResolverType) == sourceresolver.ResolverTypeOCILayout {
resolveopt.OCILayoutOpt = &sourceresolver.ResolveOCILayoutOpt{
Store: sourceresolver.ResolveImageConfigOptStore{
SessionID: req.SessionID,
StoreID: req.StoreID,
},
Platform: platform,
}
}

Expand Down Expand Up @@ -1694,6 +1705,33 @@ func getCaps(label string) map[string]struct{} {
return out
}

func toPBAttestationChain(ac *sourceresolver.AttestationChain) *pb.AttestationChain {
if ac == nil {
return nil
}
out := &pb.AttestationChain{
Root: string(ac.Root),
ImageManifest: string(ac.ImageManifest),
AttestationManifest: string(ac.AttestationManifest),
Blobs: make(map[string]*pb.Blob),
}
for _, s := range ac.SignatureManifests {
out.SignatureManifests = append(out.SignatureManifests, string(s))
}
for k, v := range ac.Blobs {
out.Blobs[k.String()] = &pb.Blob{
Descriptor_: &pb.Descriptor{
MediaType: v.Descriptor.MediaType,
Size: v.Descriptor.Size,
Digest: string(v.Descriptor.Digest),
Annotations: maps.Clone(v.Descriptor.Annotations),
},
Data: v.Data,
}
}
return out
}

func addCapsForKnownFrontends(caps map[string]struct{}, dgst digest.Digest) {
// these frontends were built without caps detection but do support inputs
defaults := map[digest.Digest]struct{}{
Expand Down
Loading
Loading