Extend the built-in policy to validate signed moby/buildkit release and floating tags before docker-container builders are created. Pull the image first, inspect it through Docker, and bind verification to the descriptor digest. Resolve signature attestations through the BuildKit API embedded in the Docker daemon. If pulling fails, use a local image while applying the same verification when the containerd image store exposes an immutable descriptor. Keep the classic image-store behavior unchanged because no descriptor is available. Allow unmanaged repositories and digest-only references unchanged. Add the allow-untrusted-image driver option as an explicit verification bypass. Document the behavior and add policy, digest-pinning, and local fallback coverage. Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
168 lines
4.6 KiB
Go
168 lines
4.6 KiB
Go
package driver
|
|
|
|
import (
|
|
"context"
|
|
"sort"
|
|
"sync"
|
|
|
|
"github.com/docker/buildx/policy"
|
|
"github.com/docker/cli/cli/context/store"
|
|
"github.com/moby/buildkit/client"
|
|
"github.com/moby/buildkit/session/auth/authprovider"
|
|
"github.com/moby/buildkit/util/tracing/delegated"
|
|
dockerclient "github.com/moby/moby/client"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
type Factory interface {
|
|
Name() string
|
|
Usage() string
|
|
Priority(ctx context.Context, endpoint string, api dockerclient.APIClient, dialMeta map[string][]string) int
|
|
New(ctx context.Context, cfg InitConfig) (Driver, error)
|
|
AllowsInstances() bool
|
|
}
|
|
|
|
type BuildkitConfig struct {
|
|
// Entitlements []string
|
|
// Rootless bool
|
|
}
|
|
|
|
// ImageVerifier validates the builder image ref against the builtin default
|
|
// policy and returns the digest that verification resolved the reference to.
|
|
// Drivers that materialize the builder from an image call it before creating
|
|
// the builder so the verified digest can be used instead of the mutable tag.
|
|
// Source metadata is resolved through the given resolver, which drivers back
|
|
// with a BuildKit instance they have access to so that registry access
|
|
// happens where the image is pulled from.
|
|
type ImageVerifier func(ctx context.Context, ref string, platform *ocispecs.Platform, resolver policy.SourceMetadataResolver) (digest.Digest, error)
|
|
|
|
type InitConfig struct {
|
|
Name string
|
|
EndpointAddr string
|
|
DockerAPI dockerclient.APIClient
|
|
ContextStore store.Reader
|
|
BuildkitdFlags []string
|
|
Files map[string][]byte
|
|
DriverOpts map[string]string
|
|
Auth authprovider.AuthConfigProvider
|
|
ImageVerifier ImageVerifier
|
|
Platforms []ocispecs.Platform
|
|
ContextPathHash string
|
|
DialMeta map[string][]string
|
|
}
|
|
|
|
var drivers map[string]Factory
|
|
|
|
func Register(f Factory) {
|
|
if drivers == nil {
|
|
drivers = map[string]Factory{}
|
|
}
|
|
drivers[f.Name()] = f
|
|
}
|
|
|
|
func GetDefaultFactory(ctx context.Context, ep string, c dockerclient.APIClient, instanceRequired bool, dialMeta map[string][]string) (Factory, error) {
|
|
if len(drivers) == 0 {
|
|
return nil, errors.Errorf("no drivers available")
|
|
}
|
|
type p struct {
|
|
f Factory
|
|
priority int
|
|
}
|
|
dd := make([]p, 0, len(drivers))
|
|
for _, f := range drivers {
|
|
if instanceRequired && !f.AllowsInstances() {
|
|
continue
|
|
}
|
|
dd = append(dd, p{f: f, priority: f.Priority(ctx, ep, c, dialMeta)})
|
|
}
|
|
sort.Slice(dd, func(i, j int) bool {
|
|
return dd[i].priority < dd[j].priority
|
|
})
|
|
return dd[0].f, nil
|
|
}
|
|
|
|
func GetFactory(name string, instanceRequired bool) (Factory, error) {
|
|
for _, f := range drivers {
|
|
if f.Name() == name {
|
|
if instanceRequired && !f.AllowsInstances() {
|
|
return nil, errors.Errorf("additional instances of driver %q cannot be created", name)
|
|
}
|
|
return f, nil
|
|
}
|
|
}
|
|
return nil, errors.Errorf("failed to find driver %q", name)
|
|
}
|
|
|
|
func GetDriver(ctx context.Context, f Factory, cfg InitConfig) (*DriverHandle, error) {
|
|
if f == nil {
|
|
var err error
|
|
f, err = GetDefaultFactory(ctx, cfg.EndpointAddr, cfg.DockerAPI, false, cfg.DialMeta)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
d, err := f.New(ctx, cfg)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &DriverHandle{Driver: d}, nil
|
|
}
|
|
|
|
func GetFactories(instanceRequired bool) []Factory {
|
|
ds := make([]Factory, 0, len(drivers))
|
|
for _, d := range drivers {
|
|
if instanceRequired && !d.AllowsInstances() {
|
|
continue
|
|
}
|
|
ds = append(ds, d)
|
|
}
|
|
sort.Slice(ds, func(i, j int) bool {
|
|
return ds[i].Name() < ds[j].Name()
|
|
})
|
|
return ds
|
|
}
|
|
|
|
type DriverHandle struct {
|
|
Driver
|
|
client *client.Client
|
|
err error
|
|
once sync.Once
|
|
historyAPISupportedOnce sync.Once
|
|
historyAPISupported bool
|
|
}
|
|
|
|
func (d *DriverHandle) Client(ctx context.Context, opt ...client.ClientOpt) (*client.Client, error) {
|
|
d.once.Do(func() {
|
|
d.client, d.err = d.Driver.Client(ctx, append(d.getClientOptions(), opt...)...)
|
|
})
|
|
return d.client, d.err
|
|
}
|
|
|
|
func (d *DriverHandle) UncachedClient(ctx context.Context) (*client.Client, error) {
|
|
return d.Driver.Client(ctx, d.getClientOptions()...)
|
|
}
|
|
|
|
func (d *DriverHandle) RequiresUncachedClient() bool {
|
|
if p, ok := d.Driver.(UncachedClientDriver); ok {
|
|
return p.RequiresUncachedClient()
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (d *DriverHandle) getClientOptions() []client.ClientOpt {
|
|
return []client.ClientOpt{
|
|
client.WithTracerDelegate(delegated.DefaultExporter),
|
|
}
|
|
}
|
|
|
|
func (d *DriverHandle) HistoryAPISupported(ctx context.Context) bool {
|
|
d.historyAPISupportedOnce.Do(func() {
|
|
if c, err := d.Client(ctx); err == nil {
|
|
d.historyAPISupported = historyAPISupported(ctx, c)
|
|
}
|
|
})
|
|
return d.historyAPISupported
|
|
}
|