policy: add GitHub attestation verification
Add github_attestation and github_release_attestation policy support. Fetch GitHub attestation bundles (including bundle_url .json.sn decode) and verify against input.http.checksum. Wire source metadata resolver progress through resolver options and add ResolveState support for policy HTTP attestation fetches. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
@@ -6,7 +6,9 @@ import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/docker/buildx/util/progress"
|
||||
"github.com/moby/buildkit/client"
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/client/llb/sourceresolver"
|
||||
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
@@ -14,37 +16,72 @@ import (
|
||||
|
||||
var _ sourceresolver.MetaResolver = &Resolver{}
|
||||
|
||||
type gatewayResolver interface {
|
||||
sourceresolver.MetaResolver
|
||||
Solve(context.Context, gwclient.SolveRequest) (*gwclient.Result, error)
|
||||
}
|
||||
|
||||
type Resolver struct {
|
||||
startOnce sync.Once
|
||||
closeOnce sync.Once
|
||||
started atomic.Bool
|
||||
mu sync.Mutex
|
||||
|
||||
ready chan sourceresolver.MetaResolver
|
||||
ready chan gatewayResolver
|
||||
done chan struct{}
|
||||
openErr error
|
||||
doneErr error
|
||||
cancel context.CancelCauseFunc
|
||||
|
||||
metaResolver sourceresolver.MetaResolver
|
||||
run func(context.Context, chan<- sourceresolver.MetaResolver) error
|
||||
metaResolver gatewayResolver
|
||||
run func(context.Context, chan<- gatewayResolver) error
|
||||
closeErr error
|
||||
}
|
||||
|
||||
func NewResolver(c *client.Client) *Resolver {
|
||||
return newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
type Option func(*newResolverOpts)
|
||||
|
||||
type newResolverOpts struct {
|
||||
progressWriter progress.Writer
|
||||
}
|
||||
|
||||
func WithProgressWriter(pw progress.Writer) Option {
|
||||
return func(o *newResolverOpts) {
|
||||
o.progressWriter = pw
|
||||
}
|
||||
}
|
||||
|
||||
func NewResolver(c *client.Client, opts ...Option) *Resolver {
|
||||
var cfg newResolverOpts
|
||||
for _, opt := range opts {
|
||||
if opt != nil {
|
||||
opt(&cfg)
|
||||
}
|
||||
}
|
||||
|
||||
return newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
var (
|
||||
statusChan chan *client.SolveStatus
|
||||
done chan struct{}
|
||||
)
|
||||
if cfg.progressWriter != nil {
|
||||
statusChan, done = progress.NewChannel(progress.WithPrefix(cfg.progressWriter, "policy", false))
|
||||
defer func() {
|
||||
<-done
|
||||
}()
|
||||
}
|
||||
|
||||
_, err := c.Build(ctx, client.SolveOpt{Internal: true}, "buildx", func(ctx context.Context, gw gwclient.Client) (*gwclient.Result, error) {
|
||||
ready <- gw
|
||||
<-ctx.Done()
|
||||
return nil, context.Cause(ctx)
|
||||
}, nil)
|
||||
}, statusChan)
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func newWithRun(run func(context.Context, chan<- sourceresolver.MetaResolver) error) *Resolver {
|
||||
func newWithRun(run func(context.Context, chan<- gatewayResolver) error) *Resolver {
|
||||
return &Resolver{
|
||||
ready: make(chan sourceresolver.MetaResolver, 1),
|
||||
ready: make(chan gatewayResolver, 1),
|
||||
done: make(chan struct{}),
|
||||
run: run,
|
||||
}
|
||||
@@ -58,6 +95,31 @@ func (r *Resolver) ResolveSourceMetadata(ctx context.Context, op *pb.SourceOp, o
|
||||
return mr.ResolveSourceMetadata(ctx, op, opt)
|
||||
}
|
||||
|
||||
func (r *Resolver) ResolveState(ctx context.Context, st llb.State) (gwclient.Reference, error) {
|
||||
mr, err := r.open(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
def, err := st.Marshal(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, err := mr.Solve(ctx, gwclient.SolveRequest{
|
||||
Definition: def.ToPB(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ref, err := res.SingleRef()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ref, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) Close() error {
|
||||
r.closeOnce.Do(func() {
|
||||
if !r.started.Load() {
|
||||
@@ -76,7 +138,7 @@ func (r *Resolver) Close() error {
|
||||
return r.closeErr
|
||||
}
|
||||
|
||||
func (r *Resolver) open(ctx context.Context) (sourceresolver.MetaResolver, error) {
|
||||
func (r *Resolver) open(ctx context.Context) (gatewayResolver, error) {
|
||||
r.startOnce.Do(func() {
|
||||
r.started.Store(true)
|
||||
buildCtx, cancel := context.WithCancelCause(context.Background())
|
||||
|
||||
@@ -8,27 +8,40 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/moby/buildkit/client/llb"
|
||||
"github.com/moby/buildkit/client/llb/sourceresolver"
|
||||
gwclient "github.com/moby/buildkit/frontend/gateway/client"
|
||||
"github.com/moby/buildkit/solver/pb"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type fakeMetaResolver struct {
|
||||
calls atomic.Int32
|
||||
resp *sourceresolver.MetaResponse
|
||||
err error
|
||||
type fakeGatewayClient struct {
|
||||
metaCalls atomic.Int32
|
||||
solveCalls atomic.Int32
|
||||
metaResp *sourceresolver.MetaResponse
|
||||
metaErr error
|
||||
solveRes *gwclient.Result
|
||||
solveErr error
|
||||
}
|
||||
|
||||
func (f *fakeMetaResolver) ResolveSourceMetadata(ctx context.Context, op *pb.SourceOp, opt sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
|
||||
f.calls.Add(1)
|
||||
return f.resp, f.err
|
||||
func (f *fakeGatewayClient) ResolveSourceMetadata(context.Context, *pb.SourceOp, sourceresolver.Opt) (*sourceresolver.MetaResponse, error) {
|
||||
f.metaCalls.Add(1)
|
||||
return f.metaResp, f.metaErr
|
||||
}
|
||||
|
||||
func (f *fakeGatewayClient) Solve(context.Context, gwclient.SolveRequest) (*gwclient.Result, error) {
|
||||
f.solveCalls.Add(1)
|
||||
if f.solveRes == nil && f.solveErr == nil {
|
||||
return nil, errors.New("unexpected solve call")
|
||||
}
|
||||
return f.solveRes, f.solveErr
|
||||
}
|
||||
|
||||
func TestResolverCloseNoopBeforeResolve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var called atomic.Int32
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
called.Add(1)
|
||||
return nil
|
||||
})
|
||||
@@ -41,8 +54,8 @@ func TestResolverResolveOpensOnce(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var runs atomic.Int32
|
||||
mr := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
mr := &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
runs.Add(1)
|
||||
ready <- mr
|
||||
<-ctx.Done()
|
||||
@@ -56,7 +69,7 @@ func TestResolverResolveOpensOnce(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
require.EqualValues(t, 1, runs.Load())
|
||||
require.EqualValues(t, 2, mr.calls.Load())
|
||||
require.EqualValues(t, 2, mr.metaCalls.Load())
|
||||
require.NoError(t, r.Close())
|
||||
}
|
||||
|
||||
@@ -64,8 +77,8 @@ func TestResolverCloseAfterOpenCancelsBuild(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var canceled atomic.Bool
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
ready <- &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
ready <- &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
|
||||
<-ctx.Done()
|
||||
canceled.Store(true)
|
||||
return context.Cause(ctx)
|
||||
@@ -82,7 +95,7 @@ func TestResolverOpenFailureIsSticky(t *testing.T) {
|
||||
|
||||
expected := errors.New("boom")
|
||||
var runs atomic.Int32
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
runs.Add(1)
|
||||
return expected
|
||||
})
|
||||
@@ -108,7 +121,7 @@ func TestResolverCloseIgnoresTerminalContextErrors(t *testing.T) {
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
return tc.err
|
||||
})
|
||||
_, err := r.ResolveSourceMetadata(t.Context(), &pb.SourceOp{}, sourceresolver.Opt{})
|
||||
@@ -122,8 +135,8 @@ func TestResolverConcurrentResolveUsesSingleOpen(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
var runs atomic.Int32
|
||||
mr := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
mr := &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
runs.Add(1)
|
||||
ready <- mr
|
||||
<-ctx.Done()
|
||||
@@ -148,7 +161,7 @@ func TestResolverConcurrentResolveUsesSingleOpen(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.EqualValues(t, 1, runs.Load())
|
||||
require.EqualValues(t, n, mr.calls.Load())
|
||||
require.EqualValues(t, n, mr.metaCalls.Load())
|
||||
|
||||
done := make(chan struct{})
|
||||
closeErr := make(chan error, 1)
|
||||
@@ -167,11 +180,11 @@ func TestResolverConcurrentResolveUsesSingleOpen(t *testing.T) {
|
||||
func TestResolverFirstCanceledContextDoesNotPoisonFutureCalls(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &fakeMetaResolver{resp: &sourceresolver.MetaResponse{}}
|
||||
mr := &fakeGatewayClient{metaResp: &sourceresolver.MetaResponse{}}
|
||||
started := make(chan struct{})
|
||||
release := make(chan struct{})
|
||||
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- sourceresolver.MetaResolver) error {
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
close(started)
|
||||
<-release
|
||||
ready <- mr
|
||||
@@ -191,3 +204,23 @@ func TestResolverFirstCanceledContextDoesNotPoisonFutureCalls(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, r.Close())
|
||||
}
|
||||
|
||||
func TestResolverResolveStateUsesSolve(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
mr := &fakeGatewayClient{solveErr: errors.New("solve boom")}
|
||||
r := newWithRun(func(ctx context.Context, ready chan<- gatewayResolver) error {
|
||||
ready <- mr
|
||||
<-ctx.Done()
|
||||
return context.Cause(ctx)
|
||||
})
|
||||
|
||||
st := llb.Scratch()
|
||||
_, err := r.ResolveState(t.Context(), st)
|
||||
require.EqualError(t, err, "solve boom")
|
||||
require.EqualValues(t, 1, mr.solveCalls.Load())
|
||||
require.NoError(t, r.Close())
|
||||
}
|
||||
|
||||
var _ gatewayResolver = (*fakeGatewayClient)(nil)
|
||||
var _ sourceresolver.MetaResolver = (*fakeGatewayClient)(nil)
|
||||
|
||||
Reference in New Issue
Block a user