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
14 changes: 1 addition & 13 deletions build/opt.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,18 +65,6 @@ var sendGitQueryAsInput = sync.OnceValue(func() bool {
return false
})

// defaultPolicyEnabled reports whether the builtin default source policy is
// enabled via the BUILDX_DEFAULT_POLICY environment variable. It is opt-in
// for now; a future release may flip the default to on.
var defaultPolicyEnabled = sync.OnceValue(func() bool {
if v, ok := os.LookupEnv("BUILDX_DEFAULT_POLICY"); ok {
if vv, err := strconv.ParseBool(v); err == nil {
return vv
}
}
return false
})

// policyExplicitlyDisabled reports whether the user passed `--policy
// disabled=true`, which suppresses both user-defined and builtin default
// policies.
Expand Down Expand Up @@ -664,7 +652,7 @@ func configureSourcePolicy(ctx context.Context, np *noderesolver.ResolvedNode, o
// (docker/dockerfile, docker/dockerfile-upstream) that may be implicitly
// loaded during a build, and passes through any other source so user
// policies retain full control.
if defaultPolicyEnabled() && !policyExplicitlyDisabled(opt.Policy) {
if policy.DefaultPolicyEnabled() && !policyExplicitlyDisabled(opt.Policy) {
builtin := policyOpt{
Files: []policyFileSpec{{
Filename: policy.DefaultPolicyFilename,
Expand Down
18 changes: 18 additions & 0 deletions builder/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,19 @@ import (

"github.com/containerd/platforms"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/policy"
"github.com/docker/buildx/store"
"github.com/docker/buildx/store/storeutil"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/dockerutil"
"github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/platformutil"
"github.com/moby/buildkit/client"
"github.com/moby/buildkit/util/grpcerrors"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sync/errgroup"
"google.golang.org/grpc/codes"
)
Expand Down Expand Up @@ -109,6 +113,19 @@ func (b *Builder) LoadNodes(ctx context.Context, opts ...LoadNodesOption) (_ []N
}
}

var imageVerifier driver.ImageVerifier
if policy.DefaultPolicyEnabled() {
pol := policy.DefaultPolicy(policy.Opt{
Log: func(_ logrus.Level, msg string) {
logrus.Debug(msg)
},
VerifierProvider: policy.SignatureVerifier(confutil.NewConfig(b.opts.dockerCli)),
})
imageVerifier = func(ctx context.Context, ref string, platform *ocispecs.Platform, resolver policy.SourceMetadataResolver) (digest.Digest, error) {
return pol.CheckSource(ctx, ref, platform, resolver)
}
}

for i, n := range b.NodeGroup.Nodes {
func(i int, n store.Node) {
eg.Go(func() error {
Expand Down Expand Up @@ -137,6 +154,7 @@ func (b *Builder) LoadNodes(ctx context.Context, opts ...LoadNodesOption) (_ []N
Files: n.Files,
DriverOpts: n.DriverOpts,
Auth: imageopt.Auth,
ImageVerifier: imageVerifier,
Platforms: n.Platforms,
ContextPathHash: b.opts.contextPathHash,
DialMeta: lno.dialMeta,
Expand Down
8 changes: 8 additions & 0 deletions docs/reference/buildx.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,11 @@ Extended build capabilities with BuildKit
### <a name="builder"></a> Override the configured builder instance (--builder)

You can also use the `BUILDX_BUILDER` environment variable.

### Enable the default policy

Set `BUILDX_DEFAULT_POLICY=1` to enable Buildx's built-in source policy. The
policy verifies signed tags for images managed by Docker, including BuildKit
builder images and Dockerfile frontends. Untagged digest references and images
outside the managed repositories are allowed unchanged. Tagged references
that also contain a digest still have their release identity verified.
12 changes: 12 additions & 0 deletions docs/reference/buildx_create.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,18 @@ documentation for the specific driver:
* [`kubernetes` driver](https://docs.docker.com/build/builders/drivers/kubernetes/)
* [`remote` driver](https://docs.docker.com/build/builders/drivers/remote/)

With `BUILDX_DEFAULT_POLICY=1`, the `docker-container` driver verifies signed
`moby/buildkit` builder image tags before creating the builder. To explicitly
bypass this verification, set `allow-untrusted-image=true`. For example:

```console
$ BUILDX_DEFAULT_POLICY=1 docker buildx create --driver docker-container \
--driver-opt allow-untrusted-image=true
```

Only use this option for an image that you trust. It disables builder image
verification for the new builder node.

### <a name="leave"></a> Remove a node from a builder (--leave)

The `--leave` flag changes the action of the command to remove a node from a
Expand Down
11 changes: 9 additions & 2 deletions driver/bkimage/bkimage.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package bkimage

const (
DefaultImage = "moby/buildkit:buildx-stable-1" // TODO: make this verified
QemuImage = "tonistiigi/binfmt:latest" // TODO: make this verified
DefaultImage = "moby/buildkit:buildx-stable-1"
QemuImage = "tonistiigi/binfmt:latest" // TODO: make this verified

@crazy-max crazy-max Jul 22, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be a follow-up but looks tricky to verify QEMU image used by the kubernetes driver during deployment at:

podTemplate.Spec.InitContainers = []corev1.Container{
{
Name: "qemu",
Image: opt.Qemu.Image,
Args: []string{"--install", "all"},
SecurityContext: &corev1.SecurityContext{
Privileged: &privileged,
},
},
}

We would need to know beforehand the node platform to perform the verification.

cc @AkihiroSuda

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The current policy verifier resolves signatures against a selected platform manifest, so Kubernetes cannot safely use the local default platform here. We'd need either a known pod platform or an all-platform verification story before verifying and pinning this init image.

DefaultRootlessImage = DefaultImage + "-rootless"

// TrustedRepo is the fully-qualified repository whose tags are verified
// against the builtin default policy before a builder is created from
// them. Images from other repositories pass through unverified; the
// allow-untrusted-image driver-opt is only needed when a TrustedRepo tag
// that the policy covers does not verify correctly.
TrustedRepo = "docker.io/moby/buildkit"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not just promote buildkit (and binfmt, xx, ...) to DOI?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would that help?

)
146 changes: 121 additions & 25 deletions driver/docker-container/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,15 @@ import (
"time"

cerrdefs "github.com/containerd/errdefs"
"github.com/containerd/platforms"
"github.com/distribution/reference"
"github.com/docker/buildx/driver"
"github.com/docker/buildx/driver/bkimage"
"github.com/docker/buildx/util/confutil"
"github.com/docker/buildx/util/ghutil"
"github.com/docker/buildx/util/imagetools"
"github.com/docker/buildx/util/progress"
"github.com/docker/buildx/util/sourcemeta"
"github.com/docker/cli/cli/context/docker"
contextstore "github.com/docker/cli/cli/context/store"
"github.com/docker/cli/opts"
Expand All @@ -29,6 +32,7 @@ import (
"github.com/moby/moby/api/types/mount"
dockerclient "github.com/moby/moby/client"
"github.com/moby/moby/client/pkg/security"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)

Expand All @@ -43,21 +47,22 @@ type Driver struct {

// if you add fields, remember to update docs:
// https://github.com/docker/docs/blob/main/content/build/drivers/docker-container.md
netMode string
image string
memory opts.MemBytes
memorySwap opts.MemSwapBytes
cpuQuota int64
cpuPeriod int64
cpuShares int64
cpusetCpus string
cpusetMems string
cgroupParent string
restartPolicy container.RestartPolicy
env []string
defaultLoad bool
gpus []container.DeviceRequest
writeProvenanceGHA bool
netMode string
image string
allowUntrustedImage bool
memory opts.MemBytes
memorySwap opts.MemSwapBytes
cpuQuota int64
cpuPeriod int64
cpuShares int64
cpusetCpus string
cpusetMems string
cgroupParent string
restartPolicy container.RestartPolicy
env []string
defaultLoad bool
gpus []container.DeviceRequest
writeProvenanceGHA bool
}

func (d *Driver) IsMobyDriver() bool {
Expand Down Expand Up @@ -92,27 +97,59 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
imageName = d.image
}

if err := l.Wrap("pulling image "+imageName, func() error {
ra, err := imagetools.RegistryAuthForRef(imageName, d.Auth)
imageRef := imageName
pullErr := l.Wrap("pulling image "+imageRef, func() error {
ra, err := imagetools.RegistryAuthForRef(imageRef, d.Auth)
if err != nil {
return err
}
resp, err := d.DockerAPI.ImagePull(ctx, imageName, dockerclient.ImagePullOptions{
resp, err := d.DockerAPI.ImagePull(ctx, imageRef, dockerclient.ImagePullOptions{
RegistryAuth: ra,
})
if err != nil {
return err
}
return resp.Wait(ctx)
}); err != nil {
// image pulling failed, check if it exists in local image store.
// if not, return pulling error. otherwise log it.
_, errInspect := d.DockerAPI.ImageInspect(ctx, imageName)
found := errInspect == nil
if !found {
})
image, inspectErr := d.DockerAPI.ImageInspect(ctx, imageRef)
if inspectErr != nil {
if pullErr != nil {
return pullErr
}
return errors.Wrapf(inspectErr, "failed to inspect pulled image %s", imageRef)
}

imageName = imageRef
if image.Descriptor != nil && d.ImageVerifier != nil && !d.allowUntrustedImage {
named, err := reference.ParseNormalizedNamed(imageRef)
if err != nil {
return errors.Wrapf(err, "failed to parse image reference %s", imageRef)
}
if named.Name() == bkimage.TrustedRepo {
if _, canonical := named.(reference.Canonical); !canonical {
named = reference.TagNameOnly(named)
}
pinned, err := reference.WithDigest(named, image.Descriptor.Digest)
if err != nil {
return errors.Wrapf(err, "failed to construct image reference for %s", imageRef)
}
imageName = pinned.String()
}
}

// Policy verification requires the immutable descriptor exposed by the
// containerd image store. Classic-store images are allowed without it.
if image.Descriptor != nil {
var err error
imageName, err = d.verifiedImageRef(ctx, l, imageName)
if err != nil {
return err
}
}
if pullErr != nil {
if err := l.Wrap("using local image "+imageName, func() error { return nil }); err != nil {
return err
}
l.Wrap("pulling failed, using local image "+imageName, func() error { return nil })
}

cfg := &container.Config{
Expand Down Expand Up @@ -229,6 +266,65 @@ func (d *Driver) create(ctx context.Context, l progress.SubLogger) error {
})
}

// verifiedImageRef evaluates ref against the builtin default policy and
// returns the canonical reference carrying the digest that verification
// resolved. Source metadata is resolved through the BuildKit embedded in the
// Docker daemon that hosts the builder, so registry access follows the
// daemon configuration. The reference is returned unchanged when the policy
// does not apply to it: policy disabled, allow-untrusted-image set, the
// image pinned by digest without a tag, or an image outside the managed
// moby/buildkit repository (which the default policy passes through).
func (d *Driver) verifiedImageRef(ctx context.Context, l progress.SubLogger, ref string) (string, error) {
if d.ImageVerifier == nil || d.allowUntrustedImage {
return ref, nil
}

c, err := d.buildkitClient(ctx)
if err != nil {
return "", errors.Wrap(err, "failed to connect to BuildKit for image verification")
}
defer c.Close()
mr := sourcemeta.NewResolver(c)
defer mr.Close()

pinned, applied, err := driver.VerifyImageRef(ctx, l, ref, d.daemonPlatform(ctx), mr, d.ImageVerifier)
if err != nil {
if !applied {
return "", err
}
return "", errors.Wrapf(err, "failed to verify image %s", ref)
}
if !applied {
return ref, nil
}
return pinned, nil
}

// buildkitClient returns a client to the BuildKit embedded in the Docker
// daemon that hosts the builder container.
func (d *Driver) buildkitClient(ctx context.Context) (*client.Client, error) {
return client.New(ctx, "",
client.WithContextDialer(func(context.Context, string) (net.Conn, error) {
return d.DockerAPI.DialHijack(ctx, "/grpc", "h2c", d.DialMeta)
}),
client.WithSessionDialer(func(ctx context.Context, proto string, meta map[string][]string) (net.Conn, error) {
return d.DockerAPI.DialHijack(ctx, "/session", proto, meta)
}),
)
}

// daemonPlatform returns the platform of the images the daemon pulls,
// falling back to the client platform when daemon info is unavailable.
func (d *Driver) daemonPlatform(ctx context.Context) *ocispecs.Platform {
if resp, err := d.DockerAPI.Info(ctx, dockerclient.InfoOptions{}); err == nil {
if p, err := platforms.Parse(resp.Info.OSType + "/" + resp.Info.Architecture); err == nil {
return &p
}
}
p := platforms.Normalize(platforms.DefaultSpec())
return &p
}

func (d *Driver) wait(ctx context.Context, l progress.SubLogger) error {
try := 1
for {
Expand Down
5 changes: 5 additions & 0 deletions driver/docker-container/factory.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,11 @@ func (f *factory) New(ctx context.Context, cfg driver.InitConfig) (driver.Driver
if err != nil {
return nil, err
}
case k == "allow-untrusted-image":
d.allowUntrustedImage, err = strconv.ParseBool(v)
if err != nil {
return nil, err
}
default:
return nil, errors.Errorf("invalid driver option %s for docker-container driver", k)
}
Expand Down
54 changes: 54 additions & 0 deletions driver/image.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package driver

import (
"context"

"github.com/distribution/reference"
"github.com/docker/buildx/driver/bkimage"
"github.com/docker/buildx/policy"
"github.com/docker/buildx/util/progress"
digest "github.com/opencontainers/go-digest"
ocispecs "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
)

// VerifyImageRef evaluates ref against the builtin default policy through
// verify and returns the reference pinned to the digest that verification
// resolved. The boolean result reports whether the policy applied: it is
// false, with ref returned unchanged, when there is no verifier, when ref is
// pinned by digest without a tag, or when ref is outside the managed
// moby/buildkit repository (unmanaged images pass through the default policy
// unchanged). A tagged canonical reference is still verified because its tag
// carries the release identity checked by the policy.
func VerifyImageRef(ctx context.Context, l progress.SubLogger, ref string, platform *ocispecs.Platform, resolver policy.SourceMetadataResolver, verify ImageVerifier) (string, bool, error) {
if verify == nil {
return ref, false, nil
}
named, err := reference.ParseNormalizedNamed(ref)
if err != nil {
return "", false, errors.Wrapf(err, "failed to parse image reference %s", ref)
}
_, isCanonical := named.(reference.Canonical)
_, isTagged := named.(reference.Tagged)
if isCanonical && !isTagged {
return ref, false, nil
}
if named.Name() != bkimage.TrustedRepo {
return ref, false, nil
}
named = reference.TagNameOnly(named)

var dgst digest.Digest
if err := l.Wrap("verifying image "+named.String(), func() error {
var err error
dgst, err = verify(ctx, named.String(), platform, resolver)
return err
}); err != nil {
return "", true, err
}
canonical, err := reference.WithDigest(named, dgst)
if err != nil {
return "", true, errors.Wrapf(err, "failed to construct canonical reference for %s", ref)
}
return canonical.String(), true, nil
}
Loading
Loading