mirror of
https://github.com/imjasonh/terraform-playground
synced 2026-07-07 23:35:16 +00:00
wheelhouse: gate sdist building behind --build-sdists opt-in (RCE risk, single-arch)
Co-authored-by: Jason Hall <imjasonh@users.noreply.github.com>
This commit is contained in:
parent
f0ba6e3f41
commit
872abd1ef7
6 changed files with 46 additions and 7 deletions
|
|
@ -72,6 +72,7 @@ type buildFlags struct {
|
|||
insecure bool
|
||||
cacheDir string
|
||||
noCache bool
|
||||
buildSdists bool
|
||||
}
|
||||
|
||||
func buildCmd() *cobra.Command {
|
||||
|
|
@ -119,6 +120,7 @@ func buildCmd() *cobra.Command {
|
|||
fs.BoolVar(&f.insecure, "insecure", false, "use plain HTTP for the registry")
|
||||
fs.StringVar(&f.cacheDir, "cache-dir", "", "cache root directory (default: per-user cache dir)")
|
||||
fs.BoolVar(&f.noCache, "no-cache", false, "disable all build caches (layers, downloaded wheels, interpreter detection)")
|
||||
fs.BoolVar(&f.buildSdists, "build-sdists", false, "allow building wheels from source distributions when no compatible wheel exists (runs the dependency's build code on this host; single-arch for compiled packages)")
|
||||
return cmd
|
||||
}
|
||||
|
||||
|
|
@ -256,7 +258,7 @@ func buildOne(ctx context.Context, f *buildFlags, lk *lock.Lock, platform *v1.Pl
|
|||
return nil, nil, err
|
||||
}
|
||||
|
||||
wheels, err := wheelhouse.ResolveContext(ctx, reqs, f.findLinks, target, wheelCache)
|
||||
wheels, err := wheelhouse.ResolveContext(ctx, reqs, f.findLinks, target, wheelCache, f.buildSdists)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,9 @@ func applyDefaults(cmd *cobra.Command, f *buildFlags) error {
|
|||
if !changed("package") && cfg.Package != "" {
|
||||
f.pkg = cfg.Package
|
||||
}
|
||||
if !changed("build-sdists") && cfg.BuildSdists {
|
||||
f.buildSdists = true
|
||||
}
|
||||
if !changed("python") && cfg.Python != "" {
|
||||
f.pythonTag = cfg.Python
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ type Config struct {
|
|||
NoCache bool `toml:"no-cache"`
|
||||
Extras []string `toml:"extras"`
|
||||
Package string `toml:"package"`
|
||||
BuildSdists bool `toml:"build-sdists"`
|
||||
}
|
||||
|
||||
const defaultBase = "cgr.dev/chainguard/python:latest"
|
||||
|
|
|
|||
|
|
@ -21,7 +21,9 @@ import (
|
|||
// returns it as a ResolvedWheel. The built wheel is cached (keyed by the sdist
|
||||
// hash and target) so it isn't rebuilt on subsequent builds.
|
||||
//
|
||||
// Building runs the host's `pip wheel`, so a Python toolchain must be present.
|
||||
// This is only reached when the caller opted in (allowSdist), because building
|
||||
// runs the dependency's own build code on the host with no isolation. Building
|
||||
// runs the host's `pip wheel`, so a Python toolchain must be present.
|
||||
// A pure-python sdist yields a `py3-none-any` wheel usable on any target; a
|
||||
// compiled sdist is built for the host platform only, so cross-arch builds of
|
||||
// such packages will fail the compatibility check with a clear error.
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package wheelhouse
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/imjasonh/terraform-playground/pymage/internal/lock"
|
||||
|
|
@ -56,6 +58,25 @@ func TestSelectBuiltWheelWrongVersion(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSdistRequiresOptIn(t *testing.T) {
|
||||
// An sdist-only requirement (no wheels) must error unless opted in, rather
|
||||
// than silently building (which runs the dependency's build code).
|
||||
req := lock.Requirement{
|
||||
Name: "timeout-decorator",
|
||||
Version: "0.5.0",
|
||||
Sdist: &lock.SdistRef{URL: "https://example/timeout_decorator-0.5.0.tar.gz", SHA256: "deadbeef", Filename: "timeout_decorator-0.5.0.tar.gz"},
|
||||
}
|
||||
target := wheel.Target{OS: "linux", Arch: "amd64", PyMajor: 3, PyMinor: 12}
|
||||
|
||||
_, err := ResolveContext(context.Background(), []lock.Requirement{req}, nil, target, t.TempDir(), false)
|
||||
if err == nil {
|
||||
t.Fatal("expected error when sdist build is not opted in")
|
||||
}
|
||||
if !strings.Contains(err.Error(), "--build-sdists") {
|
||||
t.Fatalf("error should point at the opt-in flag, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuiltPathKey(t *testing.T) {
|
||||
c := &wheelCache{dir: "/tmp/c"}
|
||||
target := wheel.Target{OS: "linux", Arch: "amd64", PyMajor: 3, PyMinor: 12}
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ type candidate struct {
|
|||
// per-user cache directory. Results are sorted by (normalized name, version)
|
||||
// for deterministic ordering.
|
||||
func Resolve(reqs []lock.Requirement, dirs []string, target wheel.Target) ([]ResolvedWheel, error) {
|
||||
return ResolveContext(context.Background(), reqs, dirs, target, defaultWheelCacheDir())
|
||||
return ResolveContext(context.Background(), reqs, dirs, target, defaultWheelCacheDir(), false)
|
||||
}
|
||||
|
||||
// defaultWheelCacheDir returns the per-user wheel download cache, or "" if the
|
||||
|
|
@ -62,8 +62,10 @@ func defaultWheelCacheDir() string {
|
|||
}
|
||||
|
||||
// ResolveContext is Resolve with an explicit context and on-disk wheel cache
|
||||
// directory for lock-file downloads.
|
||||
func ResolveContext(ctx context.Context, reqs []lock.Requirement, dirs []string, target wheel.Target, wheelCacheDir string) ([]ResolvedWheel, error) {
|
||||
// directory for lock-file downloads. When allowSdist is true, packages with no
|
||||
// compatible wheel may be built from their source distribution (which executes
|
||||
// the dependency's build code on this host); otherwise such packages error.
|
||||
func ResolveContext(ctx context.Context, reqs []lock.Requirement, dirs []string, target wheel.Target, wheelCacheDir string, allowSdist bool) ([]ResolvedWheel, error) {
|
||||
var index map[string][]candidate
|
||||
if len(dirs) > 0 {
|
||||
var err error
|
||||
|
|
@ -80,7 +82,7 @@ func ResolveContext(ctx context.Context, reqs []lock.Requirement, dirs []string,
|
|||
|
||||
out := make([]ResolvedWheel, 0, len(reqs))
|
||||
for _, req := range reqs {
|
||||
rw, err := resolveOne(ctx, req, index, target, cache)
|
||||
rw, err := resolveOne(ctx, req, index, target, cache, allowSdist)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -97,7 +99,7 @@ func ResolveContext(ctx context.Context, reqs []lock.Requirement, dirs []string,
|
|||
return out, nil
|
||||
}
|
||||
|
||||
func resolveOne(ctx context.Context, req lock.Requirement, index map[string][]candidate, target wheel.Target, cache *wheelCache) (ResolvedWheel, error) {
|
||||
func resolveOne(ctx context.Context, req lock.Requirement, index map[string][]candidate, target wheel.Target, cache *wheelCache, allowSdist bool) (ResolvedWheel, error) {
|
||||
key := lock.NormalizeName(req.Name) + "\x00" + req.Version
|
||||
if index != nil {
|
||||
if cands := index[key]; len(cands) > 0 {
|
||||
|
|
@ -141,6 +143,14 @@ func resolveOne(ctx context.Context, req lock.Requirement, index map[string][]ca
|
|||
if req.Sdist == nil {
|
||||
return ResolvedWheel{}, err
|
||||
}
|
||||
// No compatible lock wheel; consider building from the sdist below.
|
||||
}
|
||||
|
||||
// Building from an sdist runs the dependency's own build code (setup.py /
|
||||
// build backend) on this host with no container isolation, so it is gated
|
||||
// behind explicit opt-in.
|
||||
if !allowSdist {
|
||||
return ResolvedWheel{}, fmt.Errorf("wheelhouse: %s==%s has no compatible pre-built wheel and would have to be built from its source distribution, which runs arbitrary build code from the dependency on this machine; pass --build-sdists (or set build-sdists in [tool.pymage]) to allow it, or supply a pre-built wheel via --find-links", req.Name, req.Version)
|
||||
}
|
||||
return buildFromSdist(ctx, req, target, cache)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue