Skip to content

Commit 59e64f1

Browse files
authored
Merge pull request #6516 from tonistiigi/policy-resolveattestations
policy: fix ResolveAttestations via policy callback
2 parents 9836771 + 2967d38 commit 59e64f1

File tree

3 files changed

+102
-1
lines changed

3 files changed

+102
-1
lines changed

client/client_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,7 @@ var allTests = []func(t *testing.T, sb integration.Sandbox){
257257
testSourcePolicySession,
258258
testSourcePolicySessionDenyMessages,
259259
testSourceMetaPolicySession,
260+
testSourceMetaPolicySessionResolveAttestations,
260261
testSourcePolicyParallelSession,
261262
testSourcePolicySignedCommit,
262263
testSourcePolicySessionConvert,
@@ -12590,7 +12591,7 @@ func buildProvenanceImage(ctx context.Context, t *testing.T, c *Client, sb integ
1259012591

1259112592
_, err = c.Build(ctx, SolveOpt{
1259212593
FrontendAttrs: map[string]string{
12593-
"attest:provenance": "mode=max",
12594+
"attest:provenance": "mode=max,version=v1",
1259412595
},
1259512596
Exports: []ExportEntry{
1259612597
{

client/policy_test.go

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"time"
1414

1515
"github.com/containerd/platforms"
16+
intoto "github.com/in-toto/in-toto-golang/in_toto"
1617
"github.com/moby/buildkit/client/llb"
1718
"github.com/moby/buildkit/client/llb/sourceresolver"
1819
gateway "github.com/moby/buildkit/frontend/gateway/client"
@@ -21,6 +22,8 @@ import (
2122
sourcepolicypb "github.com/moby/buildkit/sourcepolicy/pb"
2223
"github.com/moby/buildkit/sourcepolicy/policysession"
2324
"github.com/moby/buildkit/util/testutil/integration"
25+
"github.com/moby/buildkit/util/testutil/workers"
26+
policyimage "github.com/moby/policy-helpers/image"
2427
digest "github.com/opencontainers/go-digest"
2528
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
2629
"github.com/pkg/errors"
@@ -270,6 +273,101 @@ func testSourceMetaPolicySession(t *testing.T, sb integration.Sandbox) {
270273
}
271274
}
272275

276+
func testSourceMetaPolicySessionResolveAttestations(t *testing.T, sb integration.Sandbox) {
277+
workers.CheckFeatureCompat(t, sb, workers.FeatureDirectPush, workers.FeatureProvenance)
278+
requiresLinux(t)
279+
280+
ctx := sb.Context()
281+
282+
c, err := New(ctx, sb.Address())
283+
require.NoError(t, err)
284+
defer c.Close()
285+
286+
target, platform := buildProvenanceImage(ctx, t, c, sb)
287+
sourceID := "docker-image://" + target
288+
requestedPredicateType := policyimage.SLSAProvenancePredicateType1
289+
290+
callbackCalls := 0
291+
p := policysession.NewPolicyProvider(func(ctx context.Context, req *policysession.CheckPolicyRequest) (*policysession.DecisionResponse, *pb.ResolveSourceMetaRequest, error) {
292+
switch callbackCalls {
293+
case 0:
294+
callbackCalls++
295+
require.Equal(t, sourceID, req.Source.Source.Identifier)
296+
require.Nil(t, req.Source.Image)
297+
return nil, &pb.ResolveSourceMetaRequest{
298+
Source: req.Source.Source,
299+
Platform: req.Platform,
300+
Image: &pb.ResolveSourceImageRequest{
301+
NoConfig: true,
302+
ResolveAttestations: []string{requestedPredicateType},
303+
},
304+
}, nil
305+
case 1:
306+
callbackCalls++
307+
require.Equal(t, sourceID, req.Source.Source.Identifier)
308+
require.NotNil(t, req.Source.Image)
309+
require.Empty(t, req.Source.Image.Config)
310+
require.NotNil(t, req.Source.Image.AttestationChain)
311+
ac := req.Source.Image.AttestationChain
312+
require.NotEmpty(t, ac.AttestationManifest)
313+
314+
att, ok := ac.Blobs[ac.AttestationManifest]
315+
require.True(t, ok)
316+
require.NotEmpty(t, att.Data)
317+
318+
var manifest ocispecs.Manifest
319+
require.NoError(t, json.Unmarshal(att.Data, &manifest))
320+
require.NotEmpty(t, manifest.Layers)
321+
322+
foundRequestedType := false
323+
324+
imageManifestDigest, err := digest.Parse(ac.ImageManifest)
325+
require.NoError(t, err)
326+
327+
for _, layer := range manifest.Layers {
328+
layerPredicateType := layer.Annotations["in-toto.io/predicate-type"]
329+
if layerPredicateType != requestedPredicateType {
330+
continue
331+
}
332+
foundRequestedType = true
333+
334+
blob, ok := ac.Blobs[string(layer.Digest)]
335+
require.True(t, ok, "missing blob for requested predicate type %q", layerPredicateType)
336+
require.NotEmpty(t, blob.Data, "empty blob data for requested predicate type %q", layerPredicateType)
337+
338+
var stmt intoto.Statement
339+
require.NoError(t, json.Unmarshal(blob.Data, &stmt))
340+
require.Equal(t, "https://in-toto.io/Statement/v0.1", stmt.Type)
341+
require.Equal(t, layerPredicateType, stmt.PredicateType)
342+
require.NotEmpty(t, stmt.Subject)
343+
require.Equal(t, imageManifestDigest.Hex(), stmt.Subject[0].Digest["sha256"])
344+
}
345+
require.True(t, foundRequestedType, "requested predicate type %q not found in attestation manifest layers", requestedPredicateType)
346+
347+
return &policysession.DecisionResponse{
348+
Action: sourcepolicypb.PolicyAction_ALLOW,
349+
}, nil, nil
350+
default:
351+
return nil, nil, errors.Errorf("too many policy callbacks: %d", callbackCalls)
352+
}
353+
})
354+
355+
_, err = c.Build(ctx, SolveOpt{
356+
SourcePolicyProvider: p,
357+
}, "test", func(ctx context.Context, c gateway.Client) (*gateway.Result, error) {
358+
_, err := c.ResolveSourceMetadata(ctx, &opspb.SourceOp{
359+
Identifier: sourceID,
360+
}, sourceresolver.Opt{
361+
ImageOpt: &sourceresolver.ResolveImageOpt{
362+
Platform: &platform,
363+
},
364+
})
365+
return nil, err
366+
}, nil)
367+
require.NoError(t, err)
368+
require.Equal(t, 2, callbackCalls)
369+
}
370+
273371
func testSourcePolicyParallelSession(t *testing.T, sb integration.Sandbox) {
274372
requiresLinux(t)
275373

solver/llbsolver/policy.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package llbsolver
22

33
import (
44
"context"
5+
"slices"
56
"strings"
67

78
"github.com/moby/buildkit/client/llb/sourceresolver"
@@ -99,6 +100,7 @@ func (p *policyEvaluator) evaluate(ctx context.Context, op *pb.Op, max int) (boo
99100
}
100101
op.ImageOpt.NoConfig = metareq.Image.NoConfig
101102
op.ImageOpt.AttestationChain = metareq.Image.AttestationChain
103+
op.ImageOpt.ResolveAttestations = slices.Clone(metareq.Image.ResolveAttestations)
102104
}
103105

104106
if metareq.Git != nil {

0 commit comments

Comments
 (0)